mirror of
https://github.com/m5stack/StackChan.git
synced 2026-05-20 11:49:45 -07:00
update firmware source code to v0.18 (#9)
This commit is contained in:
@@ -2,10 +2,10 @@
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "2.0.4")
|
||||
set(PROJECT_VER "2.1.0")
|
||||
|
||||
# Add this line to disable the specific warning
|
||||
add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(StackChan)
|
||||
project(stack-chan)
|
||||
|
||||
+61
-39
@@ -1,6 +1,6 @@
|
||||
dependencies:
|
||||
78/esp-ml307:
|
||||
component_hash: 57c8cc3309fb3da9195ec861cba3726b5cf24c362def803e7174c0b1d72e60e3
|
||||
component_hash: d4de9738baaaa5e6655c686a6083e699f3605de54dc83233ff3328cf55a4f543
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
@@ -8,7 +8,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 3.3.7
|
||||
version: 3.5.3
|
||||
78/esp-opus:
|
||||
component_hash: 8182b733f071d7bfe1e837f4c9f8649a63e4c937177f089e65772880c02f2e17
|
||||
dependencies:
|
||||
@@ -34,7 +34,7 @@ dependencies:
|
||||
type: service
|
||||
version: 2.4.1
|
||||
78/esp-wifi-connect:
|
||||
component_hash: 2613d9970df9f2203c2c549991e3bad74847b0a7facffe6698f4341e0bf73589
|
||||
component_hash: bf996fc603af1ff529668c788c7b38a8d94b1d43455af56d116d27ebb04b742a
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
@@ -42,7 +42,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 2.6.1
|
||||
version: 3.0.2
|
||||
78/esp_lcd_nv3023:
|
||||
component_hash: fa88abfc19a312eb5e6f2ffa187e0a9faf67e01e758bfb979d3f9d92561a494f
|
||||
dependencies:
|
||||
@@ -58,7 +58,7 @@ dependencies:
|
||||
type: service
|
||||
version: 1.0.0
|
||||
78/xiaozhi-fonts:
|
||||
component_hash: 51f67bcf9dec623de90d9deb72de50dfbc3634fb948b7abf27df65b386258b94
|
||||
component_hash: e5526ea3c290742963ef6ab64023669260b72aefdc37bb218c34c618962ef2a8
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
@@ -66,7 +66,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.5.4
|
||||
version: 1.5.5
|
||||
espressif/adc_battery_estimation:
|
||||
component_hash: b915167c87ed5a84b13d680bd011c2ed9a15121f1e247c6903141b9f138f3606
|
||||
dependencies:
|
||||
@@ -99,8 +99,26 @@ dependencies:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 0.2.1
|
||||
espressif/bmi270_sensor:
|
||||
component_hash: fac23a5ecd2effdac0fb0705988d853745b8ffb99a384f87a307b3b25d15d432
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
version: 0.*
|
||||
- name: espressif/i2c_bus
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: '*'
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.3'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 0.1.1
|
||||
espressif/button:
|
||||
component_hash: 4ed25fcf354b48aa5e5680b9e7549e38d7738f9b613c94a2e39119c100db9c95
|
||||
component_hash: fccb18c37f1cfe0797b74a53a44d3f400f5fd01f4993b40052dfb7f401915089
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
@@ -112,7 +130,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 4.1.4
|
||||
version: 4.1.5
|
||||
espressif/cmake_utilities:
|
||||
component_hash: 351350613ceafba240b761b4ea991e0f231ac7a9f59a9ee901f751bddc0bb18f
|
||||
dependencies:
|
||||
@@ -365,19 +383,19 @@ dependencies:
|
||||
type: service
|
||||
version: 1.2.1
|
||||
espressif/esp_lcd_touch_cst816s:
|
||||
component_hash: 51dfa9cee38d8a20f4b625d7f89120dff08c8557e613e09dfb31f53d48648235
|
||||
component_hash: 6b11c489952c121396d4eaf5804b45e3f1f8dcd2c548dc6f2cbedf68622b013d
|
||||
dependencies:
|
||||
- name: espressif/esp_lcd_touch
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: ^1.0.4
|
||||
version: ^1.2.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.1.0
|
||||
version: 1.1.1
|
||||
espressif/esp_lcd_touch_ft5x06:
|
||||
component_hash: abaec05f46a793549b60afdda9eff86e3c8e87782c8c169007911232388d2858
|
||||
dependencies:
|
||||
@@ -393,39 +411,36 @@ dependencies:
|
||||
type: service
|
||||
version: 1.0.7
|
||||
espressif/esp_lcd_touch_gt1151:
|
||||
component_hash: 69dd86cd5fad2442e3bf15c8c4ddddbe9e8739bdb26cd5f2c5249601b4c3cc38
|
||||
component_hash: acd0fb87e8b58dc90cebda8438ee31c068975c15c040a62723843ca0420d38b6
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
- name: espressif/esp_lcd_touch
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: ^1.0.4
|
||||
version: ^1.2.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.5~2
|
||||
version: 1.1.0
|
||||
espressif/esp_lcd_touch_gt911:
|
||||
component_hash: acc1c184358aa29ef72506f618c9c76a8cc2bf12af38a2bff3d44d84f3a08857
|
||||
component_hash: be02e243d18b9a661bc13b0d22c0a5cfa3f708cf04d6eb059772276c8c8a4d76
|
||||
dependencies:
|
||||
- name: espressif/esp_lcd_touch
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: ^1.1.0
|
||||
version: ^1.2.0
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.1.3
|
||||
version: 1.2.0~1
|
||||
espressif/esp_lcd_touch_st7123:
|
||||
component_hash: 3592f0dac4b3baf34c40679922026c1e22e9b925b9446e981cd1c23306ecfe65
|
||||
component_hash: 717080a55368a5ca3e3704081a75ddab3e0b8884e335e73b7094beb8ae55f195
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
@@ -434,12 +449,15 @@ dependencies:
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: ^1
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=4.4.2'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.0
|
||||
version: 1.0.1
|
||||
espressif/esp_lvgl_port:
|
||||
component_hash: bfb2778c063b05a6c47c1a8de6d166ee4d911bc86fe0b3ff9e6bab06d2812033
|
||||
component_hash: 3264cc85d86a7c32b67d26a1353a57c67e7986bb293ebb1731801936b056e632
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
@@ -451,7 +469,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 2.6.2
|
||||
version: 2.6.3
|
||||
espressif/esp_mmap_assets:
|
||||
component_hash: 91d785326b03db15e2f7f1314d8c976d38f21aa5759b570dcbbc89bcf247fd27
|
||||
dependencies:
|
||||
@@ -563,11 +581,11 @@ dependencies:
|
||||
require: private
|
||||
version: '>=4.0'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
registry_url: https://components.espressif.com
|
||||
type: service
|
||||
version: 1.5.0
|
||||
espressif/knob:
|
||||
component_hash: 23bb6b0f62d4a2bbfe8b65e182397541339b8e48d54cef15909f5d4f5bd43d29
|
||||
component_hash: 138ed090b4c9090a0a678f695b9d1884b368130c729b7333ed35bd7f4e8da80e
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
@@ -579,9 +597,9 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.1
|
||||
version: 1.0.2
|
||||
espressif/led_strip:
|
||||
component_hash: 223998f10cae6d81f2ad2dd3c1103c2221be298c708e37917482b0153f3ec64e
|
||||
component_hash: f0d1b7f93eb3ee57bcfd220656176b0b5616e1947f89ed44364555cf910c4840
|
||||
dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
@@ -589,7 +607,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 3.0.1~1
|
||||
version: 3.0.2
|
||||
espressif/usb_host_uvc:
|
||||
component_hash: b0cb9a3a2eaa09dc6e5cc3e47e04d0a73e47c2d3dcfb3afce959224a1fe5fb5e
|
||||
dependencies:
|
||||
@@ -614,7 +632,7 @@ dependencies:
|
||||
- linux
|
||||
version: 2.3.1
|
||||
espressif2022/esp_emote_gfx:
|
||||
component_hash: 7478561e4e8bd0efd4307647ec7221da2519de4ff1cd0cb08dccfbd0161cadaa
|
||||
component_hash: 67986ead33c11de895eb7ba9a069ea89949ec911ce7cf0989478cc9da2e54735
|
||||
dependencies:
|
||||
- name: espressif/cmake_utilities
|
||||
registry_url: https://components.espressif.com
|
||||
@@ -622,7 +640,7 @@ dependencies:
|
||||
version: 0.*
|
||||
- name: espressif/esp_new_jpeg
|
||||
registry_url: https://components.espressif.com
|
||||
require: private
|
||||
require: public
|
||||
version: 0.6.*
|
||||
- name: espressif/freetype
|
||||
registry_url: https://components.espressif.com
|
||||
@@ -631,10 +649,14 @@ dependencies:
|
||||
- name: idf
|
||||
require: private
|
||||
version: '>=5.0'
|
||||
- name: lvgl/lvgl
|
||||
registry_url: https://components.espressif.com
|
||||
require: public
|
||||
version: '*'
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.2.0~1
|
||||
version: 2.0.0
|
||||
espressif2022/image_player:
|
||||
component_hash: 0e42ed1c9665debd15f2f3e7e56519100e75e446410962226cb5e5402da3fa43
|
||||
dependencies:
|
||||
@@ -693,7 +715,7 @@ dependencies:
|
||||
type: service
|
||||
version: 1.0.2
|
||||
waveshare/esp_lcd_touch_cst9217:
|
||||
component_hash: c5da844e73ab7b7e02938901e71f38deed94b26976045fb4517e7fc1e8dc4db5
|
||||
component_hash: 27a00845832b7987cacf4c4125cbed5415d940987583065b01870ac080930856
|
||||
dependencies:
|
||||
- name: espressif/esp_lcd_touch
|
||||
registry_url: https://components.espressif.com
|
||||
@@ -705,7 +727,7 @@ dependencies:
|
||||
source:
|
||||
registry_url: https://components.espressif.com/
|
||||
type: service
|
||||
version: 1.0.3
|
||||
version: 1.0.4
|
||||
wvirgil123/sscma_client:
|
||||
component_hash: bf7d4a1f157303f4850d0b9b465c733ce38e8689ff40ac4743a44520524c41d5
|
||||
dependencies:
|
||||
@@ -728,6 +750,7 @@ direct_dependencies:
|
||||
- 78/xiaozhi-fonts
|
||||
- espressif/adc_battery_estimation
|
||||
- espressif/adc_mic
|
||||
- espressif/bmi270_sensor
|
||||
- espressif/button
|
||||
- espressif/cmake_utilities
|
||||
- espressif/dl_fft
|
||||
@@ -753,7 +776,6 @@ direct_dependencies:
|
||||
- espressif/esp_mmap_assets
|
||||
- espressif/esp_new_jpeg
|
||||
- espressif/esp_video
|
||||
- espressif/i2c_bus
|
||||
- espressif/knob
|
||||
- espressif/led_strip
|
||||
- espressif2022/esp_emote_gfx
|
||||
@@ -765,6 +787,6 @@ direct_dependencies:
|
||||
- waveshare/esp_lcd_sh8601
|
||||
- waveshare/esp_lcd_touch_cst9217
|
||||
- wvirgil123/sscma_client
|
||||
manifest_hash: 8b8d8d3a405eda302202a654ce1dfdbc17f486994ac285a18867b38f07a065c0
|
||||
manifest_hash: 58298729f89f9edb041dac03ee9a91c4f6f97f277634f6349f91981c9b4b715f
|
||||
target: esp32s3
|
||||
version: 2.0.0
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Define xiaozhi source directory
|
||||
set(XIAOZHI_MAIN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../xiaozhi-esp32/main")
|
||||
|
||||
|
||||
file(GLOB_RECURSE STACK_CHAN_SOURCES
|
||||
"apps/*.c"
|
||||
"apps/*.cc"
|
||||
@@ -47,6 +46,7 @@ set(SOURCES "audio/audio_codec.cc"
|
||||
"display/lvgl_display/gif/lvgl_gif.cc"
|
||||
"display/lvgl_display/gif/gifdec.c"
|
||||
"display/lvgl_display/jpg/image_to_jpeg.cpp"
|
||||
"display/lvgl_display/jpg/jpeg_to_image.c"
|
||||
"protocols/protocol.cc"
|
||||
"protocols/mqtt_protocol.cc"
|
||||
"protocols/websocket_protocol.cc"
|
||||
@@ -55,8 +55,9 @@ set(SOURCES "audio/audio_codec.cc"
|
||||
"application.cc"
|
||||
"ota.cc"
|
||||
"settings.cc"
|
||||
"device_state_event.cc"
|
||||
"device_state_machine.cc"
|
||||
"assets.cc"
|
||||
# "main.cc"
|
||||
)
|
||||
|
||||
# Transform relative paths to absolute paths from xiaozhi-esp32/main
|
||||
@@ -65,7 +66,6 @@ list(TRANSFORM SOURCES PREPEND "${XIAOZHI_MAIN_DIR}/")
|
||||
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "protocols")
|
||||
# Transform include dirs to absolute paths from xiaozhi-esp32/main
|
||||
list(TRANSFORM INCLUDE_DIRS PREPEND "${XIAOZHI_MAIN_DIR}/")
|
||||
list(APPEND INCLUDE_DIRS)
|
||||
|
||||
# Add board common files
|
||||
file(GLOB BOARD_COMMON_SOURCES ${XIAOZHI_MAIN_DIR}/boards/common/*.cc)
|
||||
@@ -99,25 +99,28 @@ if(CONFIG_BOARD_TYPE_M5STACK_STACK_CHAN)
|
||||
endif()
|
||||
|
||||
file(GLOB BOARD_SOURCES
|
||||
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.cc
|
||||
${XIAOZHI_MAIN_DIR}/boards/${BOARD_TYPE}/*.c
|
||||
)
|
||||
list(APPEND SOURCES ${BOARD_SOURCES})
|
||||
|
||||
# Select audio processor according to Kconfig
|
||||
if(CONFIG_USE_AUDIO_PROCESSOR)
|
||||
set(AUDIO_PROCESSOR_SRC "audio/processors/afe_audio_processor.cc")
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/audio/processors/afe_audio_processor.cc")
|
||||
else()
|
||||
set(AUDIO_PROCESSOR_SRC "audio/processors/no_audio_processor.cc")
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/audio/processors/no_audio_processor.cc")
|
||||
endif()
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/${AUDIO_PROCESSOR_SRC}")
|
||||
|
||||
if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4)
|
||||
set(WAKE_WORD_SRCS "audio/wake_words/afe_wake_word.cc" "audio/wake_words/custom_wake_word.cc")
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/audio/wake_words/afe_wake_word.cc")
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/audio/wake_words/custom_wake_word.cc")
|
||||
else()
|
||||
set(WAKE_WORD_SRCS "audio/wake_words/esp_wake_word.cc")
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/audio/wake_words/esp_wake_word.cc")
|
||||
endif()
|
||||
list(TRANSFORM WAKE_WORD_SRCS PREPEND "${XIAOZHI_MAIN_DIR}/")
|
||||
list(APPEND SOURCES ${WAKE_WORD_SRCS})
|
||||
|
||||
# Auto Select Additional Sources
|
||||
if (CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING)
|
||||
list(APPEND SOURCES "${XIAOZHI_MAIN_DIR}/boards/common/blufi.cpp")
|
||||
endif ()
|
||||
# Select language directory according to Kconfig
|
||||
if(CONFIG_LANGUAGE_ZH_CN)
|
||||
set(LANG_DIR "zh-CN")
|
||||
@@ -233,16 +236,14 @@ file(GLOB COMMON_SOUNDS
|
||||
|
||||
# If target chip is ESP32, exclude specific files to avoid build errors
|
||||
if(CONFIG_IDF_TARGET_ESP32)
|
||||
set(ESP32_EXCLUDE_FILES
|
||||
"audio/codecs/box_audio_codec.cc"
|
||||
"audio/codecs/es8388_audio_codec.cc"
|
||||
"audio/codecs/es8389_audio_codec.cc"
|
||||
"led/gpio_led.cc"
|
||||
"boards/common/esp32_camera.cc"
|
||||
"display/lvgl_display/jpg/image_to_jpeg.cpp"
|
||||
)
|
||||
list(TRANSFORM ESP32_EXCLUDE_FILES PREPEND "${XIAOZHI_MAIN_DIR}/")
|
||||
list(REMOVE_ITEM SOURCES ${ESP32_EXCLUDE_FILES})
|
||||
list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc"
|
||||
"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"
|
||||
)
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${SOURCES}
|
||||
@@ -264,12 +265,12 @@ target_compile_definitions(${COMPONENT_LIB}
|
||||
# Add generation rules
|
||||
add_custom_command(
|
||||
OUTPUT ${LANG_HEADER}
|
||||
COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/../xiaozhi-esp32/scripts/gen_lang.py
|
||||
COMMAND python ${PROJECT_DIR}/xiaozhi-esp32/scripts/gen_lang.py
|
||||
--language "${LANG_DIR}"
|
||||
--output "${LANG_HEADER}"
|
||||
DEPENDS
|
||||
${LANG_JSON}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../xiaozhi-esp32/scripts/gen_lang.py
|
||||
${PROJECT_DIR}/xiaozhi-esp32/scripts/gen_lang.py
|
||||
COMMENT "Generating ${LANG_DIR} language config"
|
||||
)
|
||||
|
||||
@@ -327,6 +328,7 @@ endforeach()
|
||||
|
||||
endif()
|
||||
|
||||
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)
|
||||
@@ -360,10 +362,10 @@ function(build_default_assets_bin)
|
||||
# Create custom command to build assets
|
||||
add_custom_command(
|
||||
OUTPUT ${GENERATED_ASSETS_BIN}
|
||||
COMMAND python ${CMAKE_CURRENT_SOURCE_DIR}/../xiaozhi-esp32/scripts/build_default_assets.py ${BUILD_ARGS}
|
||||
COMMAND python ${PROJECT_DIR}/xiaozhi-esp32/scripts/build_default_assets.py ${BUILD_ARGS}
|
||||
DEPENDS
|
||||
${SDKCONFIG}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../xiaozhi-esp32/scripts/build_default_assets.py
|
||||
${PROJECT_DIR}/xiaozhi-esp32/scripts/build_default_assets.py
|
||||
COMMENT "Building default assets.bin based on configuration"
|
||||
VERBATIM
|
||||
)
|
||||
@@ -421,7 +423,7 @@ function(get_assets_local_file assets_source assets_local_file_var)
|
||||
if(IS_ABSOLUTE "${assets_source}")
|
||||
set(ASSETS_LOCAL_FILE "${assets_source}")
|
||||
else()
|
||||
set(ASSETS_LOCAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${assets_source}")
|
||||
set(ASSETS_LOCAL_FILE "${XIAOZHI_MAIN_DIR}/${assets_source}")
|
||||
endif()
|
||||
|
||||
# Check if local file exists
|
||||
|
||||
@@ -153,6 +153,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_ESP_SPOT_S3
|
||||
bool "Espressif Spot-S3"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_SPOT_C5
|
||||
bool "Espressif Spot-C5"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_ESP_HI
|
||||
bool "Espressif ESP-HI"
|
||||
depends on IDF_TARGET_ESP32C3
|
||||
@@ -249,6 +252,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_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
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLED-2.06"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75
|
||||
bool "Waveshare ESP32-S3-Touch-AMOLED-1.75"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -270,6 +276,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_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
|
||||
bool "Waveshare ESP32-C6-Touch-LCD-1.83"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
@@ -285,6 +294,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_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"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5B
|
||||
bool "Waveshare ESP32-S3-Touch-LCD-3.5B"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -324,6 +336,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
|
||||
bool "Movecall Moji 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_MOVECALL_MOJI2_ESP32C5
|
||||
bool "Movecall Moji2.0 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
|
||||
bool "Movecall CuiCan 璀璨·AI吊坠"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -372,6 +387,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307
|
||||
bool "无名科技星智1.54(ML307)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
|
||||
bool "无名科技星智1.54 METAL(wifi)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
|
||||
bool "Seeed Studio SenseCAP Watcher"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -432,6 +450,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_AIPI_LITE
|
||||
bool "AIPI-Lite"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_HU_087
|
||||
bool "HU-087"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
endchoice
|
||||
|
||||
choice
|
||||
@@ -465,7 +486,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
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
|
||||
prompt "OLED Type"
|
||||
default OLED_SSD1306_128X32
|
||||
help
|
||||
@@ -659,6 +680,29 @@ config USE_AUDIO_DEBUGGER
|
||||
help
|
||||
Enable audio debugger, send audio data through UDP to the host machine
|
||||
|
||||
menu "WiFi Configuration Method"
|
||||
help
|
||||
WiFi Configuration Method Selection
|
||||
config USE_HOTSPOT_WIFI_PROVISIONING
|
||||
bool "Hotspot"
|
||||
default y
|
||||
help
|
||||
Use WiFi Hotspot to transmit WiFi configuration data
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Acoustic"
|
||||
help
|
||||
Use audio signal to transmit WiFi configuration data
|
||||
|
||||
config USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
bool "Esp Blufi"
|
||||
help
|
||||
Use esp blufi protocol to transmit WiFi configuration data
|
||||
select BT_ENABLED
|
||||
select BT_BLE_42_FEATURES_SUPPORTED
|
||||
select BT_BLE_BLUFI_ENABLE
|
||||
select MBEDTLS_DHM_C
|
||||
endmenu
|
||||
|
||||
config AUDIO_DEBUG_UDP_SERVER
|
||||
string "Audio Debug UDP Server Address"
|
||||
default "192.168.2.100:8000"
|
||||
@@ -666,12 +710,6 @@ config AUDIO_DEBUG_UDP_SERVER
|
||||
help
|
||||
UDP server address, format: IP:PORT, used to receive audio debugging data
|
||||
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Enable Acoustic WiFi Provisioning"
|
||||
default n
|
||||
help
|
||||
Enable acoustic WiFi provisioning, use audio signal to transmit WiFi configuration data
|
||||
|
||||
config RECEIVE_CUSTOM_MESSAGE
|
||||
bool "Enable Custom Message Reception"
|
||||
default n
|
||||
@@ -683,6 +721,16 @@ menu "Camera Configuration"
|
||||
|
||||
comment "Warning: Please read the help text before modifying these settings."
|
||||
|
||||
config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||
bool "Allow JPEG Input"
|
||||
default n
|
||||
help
|
||||
Allow JPEG Input format for the camera.
|
||||
|
||||
This option may need to be enabled when using a USB camera.
|
||||
|
||||
Not currently supported when used simultaneously with XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE.
|
||||
|
||||
config XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
|
||||
bool "Enable Hardware JPEG Encoder"
|
||||
default y
|
||||
@@ -691,6 +739,14 @@ menu "Camera Configuration"
|
||||
Use hardware JPEG encoder on ESP32-P4 to encode image to JPEG.
|
||||
See https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html for more details.
|
||||
|
||||
config XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
|
||||
bool "Enable Hardware JPEG Decoder"
|
||||
default n
|
||||
depends on SOC_JPEG_DECODE_SUPPORTED && XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||
help
|
||||
Use hardware JPEG decoder on ESP32-P4 to decode JPEG to image.
|
||||
See https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html for more details.
|
||||
|
||||
config XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
|
||||
bool "Enable Camera Debug Mode"
|
||||
default n
|
||||
@@ -699,7 +755,7 @@ menu "Camera Configuration"
|
||||
Only works on boards that support camera.
|
||||
|
||||
config XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
|
||||
bool "Enable software camera buffer endianness swapping (USE WITH CAUTION)"
|
||||
bool "Enable software camera buffer endianness swapping"
|
||||
default n
|
||||
depends on !CAMERA_SENSOR_SWAP_PIXEL_BYTE_ORDER
|
||||
help
|
||||
@@ -714,12 +770,15 @@ menu "Camera Configuration"
|
||||
menuconfig XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||||
bool "Enable Camera Image Rotation"
|
||||
default n
|
||||
depends on !XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||
help
|
||||
Enable camera image rotation, rotate the camera image to the correct orientation.
|
||||
- On ESP32-P4, rotation is handled by PPA hardware.
|
||||
- On other chips, rotation is done in software with performance cost.
|
||||
- For 180° rotation, use HFlip + VFlip instead of this option.
|
||||
|
||||
Not currently supported when used simultaneously with XIAOZHI_CAMERA_ALLOW_JPEG_INPUT.
|
||||
|
||||
if XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
|
||||
choice XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE
|
||||
prompt "Camera Image Rotation Angle (clockwise)"
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <mooncake_log.h>
|
||||
#include <assets/assets.h>
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <apps/common/common.h>
|
||||
|
||||
using namespace mooncake;
|
||||
using namespace smooth_ui_toolkit::lvgl_cpp;
|
||||
@@ -36,7 +38,9 @@ void AppAiAgent::onOpen()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on open");
|
||||
|
||||
GetHAL().startXiaozhi();
|
||||
// Request to start Xiaozhi service
|
||||
// All apps will be uninstall in next mooncake update
|
||||
GetHAL().requestXiaozhiStart();
|
||||
}
|
||||
|
||||
// Called repeatedly while the App is running
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <assets/assets.h>
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <apps/common/common.h>
|
||||
#include <string_view>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
@@ -62,18 +63,32 @@ void AppAvatar::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>(0xFF6699, 0x431525);
|
||||
}
|
||||
|
||||
// Start avatar service
|
||||
GetHAL().startWebSocketAvatarService([&](std::string_view msg) {
|
||||
LvglLockGuard lock;
|
||||
loading_page->setMessage(msg);
|
||||
});
|
||||
// GetHAL().startBleServer();
|
||||
GetHAL().startWebSocketAvatar();
|
||||
|
||||
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 ------------------------------- */
|
||||
_ble_avatar_data.callback_id = GetHAL().onBleAvatarData.connect([&](const char* data) {
|
||||
GetHAL().onBleAvatarData.connect([&](const char* data) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_ble_avatar_data.update_flag) {
|
||||
return;
|
||||
@@ -82,7 +97,7 @@ void AppAvatar::onOpen()
|
||||
_ble_avatar_data.data_ptr = (char*)data;
|
||||
});
|
||||
|
||||
_ble_motion_data.callback_id = GetHAL().onBleMotionData.connect([&](const char* data) {
|
||||
GetHAL().onBleMotionData.connect([&](const char* data) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
if (_ble_motion_data.update_flag) {
|
||||
return;
|
||||
@@ -93,20 +108,20 @@ void AppAvatar::onOpen()
|
||||
|
||||
/* ---------------------------- Websocket events ---------------------------- */
|
||||
// Avatar control
|
||||
_ws_callback_ids.avatar_id = GetHAL().onWsAvatarData.connect([&](std::string_view data) {
|
||||
GetHAL().onWsAvatarData.connect([&](std::string_view data) {
|
||||
LvglLockGuard lvgl_lock;
|
||||
GetStackChan().updateAvatarFromJson(data.data());
|
||||
});
|
||||
|
||||
// Motion control
|
||||
_ws_callback_ids.motion_id = GetHAL().onWsMotionData.connect([&](std::string_view data) {
|
||||
GetHAL().onWsMotionData.connect([&](std::string_view data) {
|
||||
LvglLockGuard lvgl_lock;
|
||||
check_auto_angle_sync_mode();
|
||||
GetStackChan().updateMotionFromJson(data.data());
|
||||
});
|
||||
|
||||
// Phone call handling
|
||||
_ws_callback_ids.call_req_id = GetHAL().onWsCallRequest.connect([&](std::string caller) {
|
||||
GetHAL().onWsCallRequest.connect([&](std::string caller) {
|
||||
if (_ws_call_view_id >= 0) {
|
||||
mclog::tagWarn(getAppInfo().name, "ws call view already exists");
|
||||
return;
|
||||
@@ -145,7 +160,7 @@ void AppAvatar::onOpen()
|
||||
_ws_call_view_id = avatar.addDecorator(std::move(view));
|
||||
});
|
||||
|
||||
_ws_callback_ids.call_end_id = GetHAL().onWsCallEnd.connect([&](WsSignalSource source) {
|
||||
GetHAL().onWsCallEnd.connect([&](WsSignalSource source) {
|
||||
if (source != WsSignalSource::Remote) {
|
||||
return;
|
||||
}
|
||||
@@ -168,11 +183,10 @@ void AppAvatar::onOpen()
|
||||
});
|
||||
|
||||
// Text message handling
|
||||
_ws_callback_ids.text_msg_id = GetHAL().onWsTextMessage.connect([&](const WsTextMessage_t& message) {
|
||||
GetHAL().onWsTextMessage.connect([&](const WsTextMessage_t& message) {
|
||||
LvglLockGuard lvgl_lock;
|
||||
|
||||
auto& stackchan = GetStackChan();
|
||||
auto& avatar = stackchan.avatar();
|
||||
|
||||
stackchan.addModifier(
|
||||
std::make_unique<TimedSpeechModifier>(fmt::format("{} says: {}", message.name, message.content), 6000));
|
||||
@@ -186,7 +200,7 @@ void AppAvatar::onOpen()
|
||||
}
|
||||
});
|
||||
|
||||
_ws_callback_ids.dance_data_id = GetHAL().onWsDanceData.connect([&](std::string_view data) {
|
||||
GetHAL().onWsDanceData.connect([&](std::string_view data) {
|
||||
LvglLockGuard lvgl_lock;
|
||||
auto sequence = stackchan::animation::parse_sequence_from_json(data.data());
|
||||
if (!sequence.empty()) {
|
||||
@@ -194,8 +208,18 @@ void AppAvatar::onOpen()
|
||||
}
|
||||
});
|
||||
|
||||
GetHAL().onWsLog.connect([&](CommonLogLevel level, std::string_view msg) {
|
||||
auto type = static_cast<view::ToastType>(level);
|
||||
uint32_t duration = type == view::ToastType::Error ? 12000 : 1600;
|
||||
view::pop_a_toast(msg, type, duration);
|
||||
});
|
||||
|
||||
/* ------------------------------ Video window ------------------------------ */
|
||||
_video_window = std::make_unique<view::VideoWindow>(lv_screen_active());
|
||||
|
||||
/* ----------------------------- Common widgets ----------------------------- */
|
||||
view::create_home_indicator([&]() { close(); }, 0xFF9ABC, 0x431525);
|
||||
view::create_status_bar(0xFF9ABC, 0x431525);
|
||||
}
|
||||
|
||||
void AppAvatar::onRunning()
|
||||
@@ -218,26 +242,36 @@ void AppAvatar::onRunning()
|
||||
}
|
||||
|
||||
GetStackChan().update();
|
||||
|
||||
view::update_home_indicator();
|
||||
view::update_status_bar();
|
||||
}
|
||||
|
||||
void AppAvatar::onClose()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
|
||||
LvglLockGuard lock;
|
||||
{
|
||||
LvglLockGuard lock;
|
||||
|
||||
GetStackChan().resetAvatar();
|
||||
_video_window.reset();
|
||||
GetStackChan().resetAvatar();
|
||||
_video_window.reset();
|
||||
|
||||
GetHAL().onBleAvatarData.disconnect(_ble_avatar_data.callback_id);
|
||||
GetHAL().onBleMotionData.disconnect(_ble_motion_data.callback_id);
|
||||
GetHAL().onBleAvatarData.clear();
|
||||
GetHAL().onBleMotionData.clear();
|
||||
|
||||
GetHAL().onWsAvatarData.disconnect(_ws_callback_ids.avatar_id);
|
||||
GetHAL().onWsMotionData.disconnect(_ws_callback_ids.motion_id);
|
||||
GetHAL().onWsCallRequest.disconnect(_ws_callback_ids.call_req_id);
|
||||
GetHAL().onWsCallEnd.disconnect(_ws_callback_ids.call_end_id);
|
||||
GetHAL().onWsTextMessage.disconnect(_ws_callback_ids.text_msg_id);
|
||||
GetHAL().onWsDanceData.disconnect(_ws_callback_ids.dance_data_id);
|
||||
GetHAL().onWsAvatarData.clear();
|
||||
GetHAL().onWsMotionData.clear();
|
||||
GetHAL().onWsCallRequest.clear();
|
||||
GetHAL().onWsCallEnd.clear();
|
||||
GetHAL().onWsTextMessage.clear();
|
||||
GetHAL().onWsDanceData.clear();
|
||||
|
||||
view::destroy_home_indicator();
|
||||
view::destroy_status_bar();
|
||||
}
|
||||
|
||||
GetHAL().requestWarmReboot(1);
|
||||
}
|
||||
|
||||
void AppAvatar::check_auto_angle_sync_mode()
|
||||
|
||||
@@ -31,21 +31,10 @@ private:
|
||||
struct BleHandlerData_t {
|
||||
bool update_flag = false;
|
||||
char* data_ptr = nullptr;
|
||||
int callback_id = -1;
|
||||
};
|
||||
BleHandlerData_t _ble_avatar_data;
|
||||
BleHandlerData_t _ble_motion_data;
|
||||
|
||||
struct WsCallbackIds_t {
|
||||
int avatar_id = -1;
|
||||
int motion_id = -1;
|
||||
int call_req_id = -1;
|
||||
int call_end_id = -1;
|
||||
int text_msg_id = -1;
|
||||
int dance_data_id = -1;
|
||||
};
|
||||
WsCallbackIds_t _ws_callback_ids;
|
||||
|
||||
int _ws_call_view_id = -1;
|
||||
|
||||
uint32_t _last_motion_cmd_tick = 0;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
*/
|
||||
#include "app_espnow_ctrl.h"
|
||||
#include "view/page_selector.h"
|
||||
#include "view/view.h"
|
||||
#include <hal/hal.h>
|
||||
#include <mooncake.h>
|
||||
#include <mooncake_log.h>
|
||||
#include <assets/assets.h>
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <apps/common/common.h>
|
||||
#include <cstdint>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
@@ -26,7 +28,7 @@ AppEspnowControl::AppEspnowControl()
|
||||
// 配置 App 图标
|
||||
setAppInfo().icon = (void*)&icon_controller;
|
||||
// 配置 App 主题颜色
|
||||
static uint32_t theme_color = 0xCCCC33;
|
||||
static uint32_t theme_color = 0x7ACE74;
|
||||
setAppInfo().userData = (void*)&theme_color;
|
||||
}
|
||||
|
||||
@@ -38,36 +40,27 @@ void AppEspnowControl::onCreate()
|
||||
static std::mutex _mutex;
|
||||
static std::vector<uint8_t> _received_data;
|
||||
static int _receiver_id = 0;
|
||||
static int _wifi_channel = 0;
|
||||
static bool _is_receiver = false;
|
||||
|
||||
void AppEspnowControl::onOpen()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on open");
|
||||
|
||||
// Get role
|
||||
std::vector<std::string> role_options = {"Receiver", "Sender"};
|
||||
int role_selection = view::create_page_selector_and_wait("Select Role", role_options);
|
||||
_is_receiver = (role_selection == 0);
|
||||
mclog::tagInfo(getAppInfo().name, "selected role: {}", _is_receiver ? "Receiver" : "Sender");
|
||||
// Default setup
|
||||
_receiver_id = 1;
|
||||
_wifi_channel = 1;
|
||||
|
||||
// Get channel
|
||||
std::vector<std::string> channel_options;
|
||||
for (int i = 0; i < 13; i++) {
|
||||
channel_options.push_back(std::to_string(i + 1));
|
||||
// Start setup page
|
||||
bool is_advanced = start_startup_page();
|
||||
if (is_advanced) {
|
||||
start_advanced_page();
|
||||
}
|
||||
auto wifi_channel = view::create_page_selector_and_wait("Select WiFi Channel", channel_options) + 1;
|
||||
mclog::tagInfo(getAppInfo().name, "selected wifi channel: {}", wifi_channel);
|
||||
|
||||
// Get id
|
||||
std::vector<std::string> id_options;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
id_options.push_back(std::to_string(i));
|
||||
}
|
||||
_receiver_id = view::create_page_selector_and_wait("Select ID", id_options);
|
||||
mclog::tagInfo(getAppInfo().name, "selected id: {}", _receiver_id);
|
||||
mclog::tagInfo(getAppInfo().name, "get setup: role {}, id {}, channel {}", _is_receiver ? "Receiver" : "Sender",
|
||||
_receiver_id, _wifi_channel);
|
||||
|
||||
// Start espnow
|
||||
GetHAL().startEspNow(wifi_channel);
|
||||
GetHAL().startEspNow(_wifi_channel);
|
||||
GetHAL().onEspNowData.connect([](const std::vector<uint8_t>& data) {
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
_received_data = data;
|
||||
@@ -82,6 +75,7 @@ void AppEspnowControl::onOpen()
|
||||
avatar->init(lv_screen_active());
|
||||
stackchan.attachAvatar(std::move(avatar));
|
||||
|
||||
stackchan.clearModifiers();
|
||||
stackchan.addModifier(std::make_unique<BreathModifier>());
|
||||
stackchan.addModifier(std::make_unique<BlinkModifier>());
|
||||
|
||||
@@ -89,6 +83,75 @@ void AppEspnowControl::onOpen()
|
||||
stackchan.motion().setAutoAngleSyncEnabled(false);
|
||||
|
||||
GetHAL().setLaserEnabled(false);
|
||||
|
||||
view::create_home_indicator([&]() { close(); }, 0xA0D99C, 0x154311);
|
||||
view::create_status_bar(0xA0D99C, 0x154311);
|
||||
}
|
||||
|
||||
bool AppEspnowControl::start_startup_page()
|
||||
{
|
||||
GetHAL().lvglLock();
|
||||
auto page = std::make_unique<view::EspnowRoleSelectorPage>();
|
||||
GetHAL().lvglUnlock();
|
||||
while (1) {
|
||||
GetHAL().delay(50);
|
||||
LvglLockGuard lock;
|
||||
if (page->isSelected()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool is_advanced = false;
|
||||
GetHAL().lvglLock();
|
||||
if (page->selectedIndex() == 0) {
|
||||
_is_receiver = true;
|
||||
} else if (page->selectedIndex() == 1) {
|
||||
_is_receiver = false;
|
||||
} else {
|
||||
is_advanced = true;
|
||||
}
|
||||
page.reset();
|
||||
GetHAL().lvglUnlock();
|
||||
|
||||
return is_advanced;
|
||||
}
|
||||
|
||||
void AppEspnowControl::start_advanced_page()
|
||||
{
|
||||
// Get role
|
||||
std::vector<std::string> role_options = {"Receiver", "Sender"};
|
||||
int role_selection = view::create_page_selector_and_wait("Select Role", role_options);
|
||||
_is_receiver = (role_selection == 0);
|
||||
mclog::tagInfo(getAppInfo().name, "selected role: {}", _is_receiver ? "Receiver" : "Sender");
|
||||
|
||||
// Get wifi channel
|
||||
std::vector<std::string> channel_options;
|
||||
for (int i = 0; i < 13; i++) {
|
||||
channel_options.push_back(std::to_string(i + 1));
|
||||
}
|
||||
_wifi_channel = view::create_page_selector_and_wait("Select WiFi Channel", channel_options) + 1;
|
||||
mclog::tagInfo(getAppInfo().name, "selected wifi channel: {}", _wifi_channel);
|
||||
|
||||
// Get id
|
||||
if (_is_receiver) {
|
||||
std::vector<std::string> id_options;
|
||||
for (int i = 1; i < 255; i++) {
|
||||
id_options.push_back(std::to_string(i));
|
||||
}
|
||||
_receiver_id = view::create_page_selector_and_wait("Select Receiver ID", id_options) + 1;
|
||||
mclog::tagInfo(getAppInfo().name, "selected receiver id: {}", _receiver_id);
|
||||
} else {
|
||||
std::vector<std::string> id_options;
|
||||
for (int i = 0; i < 255; i++) {
|
||||
if (i == 0) {
|
||||
id_options.push_back("0 (Broadcast)");
|
||||
continue;
|
||||
}
|
||||
id_options.push_back(std::to_string(i));
|
||||
}
|
||||
_receiver_id = view::create_page_selector_and_wait("Select Receiver ID", id_options);
|
||||
mclog::tagInfo(getAppInfo().name, "selected target id: {}", _receiver_id);
|
||||
}
|
||||
}
|
||||
|
||||
void handle_received_data()
|
||||
@@ -178,6 +241,9 @@ void AppEspnowControl::onRunning()
|
||||
}
|
||||
|
||||
GetStackChan().update();
|
||||
|
||||
view::update_home_indicator();
|
||||
view::update_status_bar();
|
||||
}
|
||||
|
||||
void AppEspnowControl::onClose()
|
||||
@@ -185,4 +251,9 @@ void AppEspnowControl::onClose()
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
view::destroy_home_indicator();
|
||||
view::destroy_status_bar();
|
||||
|
||||
GetHAL().requestWarmReboot(2);
|
||||
}
|
||||
|
||||
@@ -19,4 +19,8 @@ public:
|
||||
void onOpen() override;
|
||||
void onRunning() override;
|
||||
void onClose() override;
|
||||
|
||||
private:
|
||||
bool start_startup_page();
|
||||
void start_advanced_page();
|
||||
};
|
||||
|
||||
@@ -20,11 +20,16 @@ public:
|
||||
{
|
||||
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
|
||||
_panel->setPadding(0, 0, 0, 0);
|
||||
_panel->setBgColor(lv_color_hex(0xF6F6F6));
|
||||
_panel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
_panel->setBorderWidth(0);
|
||||
_panel->setSize(320, 240);
|
||||
_panel->setRadius(0);
|
||||
|
||||
_label = std::make_unique<uitk::lvgl_cpp::Label>(_panel->get());
|
||||
_label->setText(label);
|
||||
_label->setTextFont(&lv_font_montserrat_24);
|
||||
_label->setTextColor(lv_color_hex(0x26206A));
|
||||
_label->align(LV_ALIGN_CENTER, 0, -80);
|
||||
|
||||
_roller = std::make_unique<uitk::lvgl_cpp::Roller>(_panel->get());
|
||||
@@ -33,12 +38,20 @@ public:
|
||||
_roller->align(LV_ALIGN_CENTER, -45, 35);
|
||||
_roller->onValueChanged().connect([&](uint32_t index) { _selected_index = index; });
|
||||
_roller->setTextFont(&lv_font_montserrat_24);
|
||||
_roller->setTextColor(lv_color_hex(0x26206A));
|
||||
_roller->setBgColor(lv_color_hex(0xDDEAFF));
|
||||
_roller->setRadius(18);
|
||||
_roller->setShadowWidth(0);
|
||||
_roller->setBorderWidth(0);
|
||||
|
||||
_btn_confirm = std::make_unique<uitk::lvgl_cpp::Button>(_panel->get());
|
||||
_btn_confirm->label().setText("ok");
|
||||
_btn_confirm->label().setTextFont(&lv_font_montserrat_24);
|
||||
_btn_confirm->setSize(70, 110);
|
||||
_btn_confirm->align(LV_ALIGN_CENTER, 110, 40);
|
||||
_btn_confirm->onClick().connect([&]() { _is_selected = true; });
|
||||
_btn_confirm->setRadius(18);
|
||||
_btn_confirm->setShadowWidth(0);
|
||||
}
|
||||
|
||||
bool update()
|
||||
@@ -60,6 +73,13 @@ private:
|
||||
int _selected_index = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Create a page selector and wait object
|
||||
*
|
||||
* @param label
|
||||
* @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)
|
||||
{
|
||||
GetHAL().lvglLock();
|
||||
|
||||
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include <hal/hal.h>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace view {
|
||||
|
||||
class EspnowRoleSelectorPage {
|
||||
public:
|
||||
EspnowRoleSelectorPage()
|
||||
{
|
||||
_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(0xE7FFE0));
|
||||
_panel->setPadding(0, 0, 0, 0);
|
||||
_panel->setPadRow(18);
|
||||
_panel->setRadius(0);
|
||||
_panel->setBorderWidth(0);
|
||||
|
||||
_title = std::make_unique<uitk::lvgl_cpp::Label>(*_panel);
|
||||
_title->setText("Select Role:");
|
||||
_title->setTextFont(&lv_font_montserrat_24);
|
||||
_title->setTextColor(lv_color_hex(0x154311));
|
||||
_title->align(LV_ALIGN_TOP_MID, 0, 12);
|
||||
|
||||
_btn_receiver = std::make_unique<uitk::lvgl_cpp::Button>(*_panel);
|
||||
_btn_receiver->align(LV_ALIGN_TOP_MID, 0, 58);
|
||||
_btn_receiver->setSize(282, 48);
|
||||
_btn_receiver->setRadius(18);
|
||||
_btn_receiver->setBgColor(lv_color_hex(0xA0D99C));
|
||||
_btn_receiver->setBorderWidth(0);
|
||||
_btn_receiver->setShadowWidth(0);
|
||||
_btn_receiver->label().setText("Receiver");
|
||||
_btn_receiver->label().setTextFont(&lv_font_montserrat_24);
|
||||
_btn_receiver->label().setTextColor(lv_color_hex(0x154311));
|
||||
_btn_receiver->onClick().connect([this]() { _selected_index = 0; });
|
||||
|
||||
_btn_sender = std::make_unique<uitk::lvgl_cpp::Button>(*_panel);
|
||||
_btn_sender->align(LV_ALIGN_TOP_MID, 0, 126);
|
||||
_btn_sender->setSize(282, 48);
|
||||
_btn_sender->setRadius(18);
|
||||
_btn_sender->setBgColor(lv_color_hex(0xA0D99C));
|
||||
_btn_sender->setBorderWidth(0);
|
||||
_btn_sender->setShadowWidth(0);
|
||||
_btn_sender->label().setText("Sender");
|
||||
_btn_sender->label().setTextFont(&lv_font_montserrat_24);
|
||||
_btn_sender->label().setTextColor(lv_color_hex(0x154311));
|
||||
_btn_sender->onClick().connect([this]() { _selected_index = 1; });
|
||||
|
||||
_btn_advanced = std::make_unique<uitk::lvgl_cpp::Button>(*_panel);
|
||||
_btn_advanced->align(LV_ALIGN_TOP_MID, 0, 194);
|
||||
_btn_advanced->setSize(170, 30);
|
||||
_btn_advanced->setRadius(18);
|
||||
_btn_advanced->setBgColor(lv_color_hex(0xB9E6B4));
|
||||
_btn_advanced->setBorderWidth(0);
|
||||
_btn_advanced->setShadowWidth(0);
|
||||
_btn_advanced->label().setText("Advanced");
|
||||
_btn_advanced->label().setTextFont(&lv_font_montserrat_16);
|
||||
_btn_advanced->label().setTextColor(lv_color_hex(0x5F8559));
|
||||
_btn_advanced->onClick().connect([this]() { _selected_index = 2; });
|
||||
}
|
||||
|
||||
bool isSelected()
|
||||
{
|
||||
return _selected_index != -1;
|
||||
}
|
||||
|
||||
int selectedIndex()
|
||||
{
|
||||
return _selected_index;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_receiver;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_sender;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_advanced;
|
||||
int _selected_index = -1;
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
@@ -26,21 +26,29 @@ void AppLauncher::onLauncherOpen()
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_view = std::make_unique<view::LauncherView>();
|
||||
_view->init(getAppProps());
|
||||
_view->onAppClicked = [&](int appID) {
|
||||
mclog::tagInfo(getAppInfo().name, "handle open app, app id: {}", appID);
|
||||
openApp(appID);
|
||||
};
|
||||
if (!_startup_checked && !GetHAL().isAppConfiged()) {
|
||||
mclog::tagInfo(getAppInfo().name, "app not configured, start startup worker");
|
||||
_startup_worker = std::make_unique<setup_workers::StartupWorker>();
|
||||
} else {
|
||||
create_launcher_view();
|
||||
}
|
||||
}
|
||||
|
||||
void AppLauncher::onLauncherRunning()
|
||||
{
|
||||
LvglLockGuard lock;
|
||||
|
||||
_view->update();
|
||||
|
||||
screensaver_update();
|
||||
if (_startup_worker) {
|
||||
_startup_worker->update();
|
||||
if (_startup_worker->isDone()) {
|
||||
_startup_worker.reset();
|
||||
_startup_checked = true;
|
||||
create_launcher_view();
|
||||
}
|
||||
} else {
|
||||
_view->update();
|
||||
screensaver_update();
|
||||
}
|
||||
|
||||
GetStackChan().update();
|
||||
}
|
||||
@@ -59,6 +67,16 @@ void AppLauncher::onLauncherDestroy()
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
}
|
||||
|
||||
void AppLauncher::create_launcher_view()
|
||||
{
|
||||
_view = std::make_unique<view::LauncherView>();
|
||||
_view->init(getAppProps());
|
||||
_view->onAppClicked = [&](int appID) {
|
||||
mclog::tagInfo(getAppInfo().name, "handle open app, app id: {}", appID);
|
||||
openApp(appID);
|
||||
};
|
||||
}
|
||||
|
||||
void AppLauncher::screensaver_update()
|
||||
{
|
||||
const uint32_t SCREENSAVER_TIMEOUT_MS = 30000;
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
#pragma once
|
||||
#include "view/view.h"
|
||||
#include <apps/app_setup/workers/workers.h>
|
||||
#include <mooncake.h>
|
||||
#include <mooncake_templates.h>
|
||||
#include <cstdint>
|
||||
@@ -21,7 +22,10 @@ public:
|
||||
private:
|
||||
std::unique_ptr<view::LauncherView> _view;
|
||||
std::unique_ptr<view::Screensaver> _screensaver;
|
||||
|
||||
std::unique_ptr<setup_workers::StartupWorker> _startup_worker;
|
||||
uint32_t _screensaver_timecount = 0;
|
||||
bool _startup_checked = false;
|
||||
|
||||
void create_launcher_view();
|
||||
void screensaver_update();
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <mooncake_log.h>
|
||||
#include <assets/assets.h>
|
||||
#include <functional>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
@@ -134,12 +135,13 @@ public:
|
||||
{
|
||||
_last_index = _current_index;
|
||||
|
||||
_current_index = (scrollValue + _page_gap / 2) / _page_gap;
|
||||
// Calculate absolute index
|
||||
int abs_index = (scrollValue + _page_gap / 2) / _page_gap;
|
||||
|
||||
// Map to 0 ~ N-1
|
||||
_current_index = abs_index % _page_num;
|
||||
if (_current_index < 0) {
|
||||
_current_index = 0;
|
||||
}
|
||||
if (_current_index >= _page_num) {
|
||||
_current_index = _page_num - 1;
|
||||
_current_index += _page_num;
|
||||
}
|
||||
|
||||
if (_last_index != _current_index) {
|
||||
@@ -282,6 +284,9 @@ private:
|
||||
|
||||
static std::string _tag = "LauncherView";
|
||||
static constexpr int _icon_gap = 320;
|
||||
// Create 5 copies: [0:Backup] [1:Buffer] [2:Main] [3:Buffer] [4:Backup]
|
||||
static constexpr int _loop_copies = 5;
|
||||
static constexpr int _center_copy_index = 2;
|
||||
|
||||
static int _last_clicked_icon_pos_x = -1;
|
||||
static std::unique_ptr<DynamicBgColor> _dynamic_bg_color;
|
||||
@@ -323,54 +328,55 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
int icon_x = 0;
|
||||
int icon_y = 0;
|
||||
std::vector<std::string> icon_label_texts;
|
||||
for (const auto& props : appPorps) {
|
||||
// mclog::tagInfo(_tag, "name: {}, id: {}", props.info.name, props.appID);
|
||||
std::vector<uint32_t> step_colors;
|
||||
|
||||
// Icon panel
|
||||
_icon_panels.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_icon_panels.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_icon_panels.back()->setSize(190, 160);
|
||||
_icon_panels.back()->setPos(icon_x, icon_y);
|
||||
_icon_panels.back()->setBorderWidth(0);
|
||||
_icon_panels.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_icon_panels.back()->setBgOpa(0);
|
||||
// Loop multiple times to create fake infinite scroll
|
||||
for (int loop = 0; loop < _loop_copies; loop++) {
|
||||
for (const auto& props : appPorps) {
|
||||
// Icon panel
|
||||
_icon_panels.push_back(std::make_unique<Container>(_panel->get()));
|
||||
_icon_panels.back()->setAlign(LV_ALIGN_CENTER);
|
||||
_icon_panels.back()->setSize(190, 160);
|
||||
_icon_panels.back()->setPos(icon_x, icon_y);
|
||||
_icon_panels.back()->setBorderWidth(0);
|
||||
_icon_panels.back()->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_icon_panels.back()->setBgOpa(0);
|
||||
|
||||
// Icon click callback
|
||||
auto app_id = props.appID;
|
||||
auto pos_x = icon_x;
|
||||
_icon_panels.back()->onClick().connect([&, app_id, pos_x]() {
|
||||
_clicked_app_id = app_id;
|
||||
_last_clicked_icon_pos_x = pos_x;
|
||||
});
|
||||
// Icon click callback
|
||||
auto app_id = props.appID;
|
||||
auto pos_x = icon_x;
|
||||
_icon_panels.back()->onClick().connect([&, app_id, pos_x]() {
|
||||
_clicked_app_id = app_id;
|
||||
_last_clicked_icon_pos_x = pos_x;
|
||||
});
|
||||
|
||||
// Collect icon label texts
|
||||
icon_label_texts.push_back(props.info.name);
|
||||
// Keep track of data for helpers
|
||||
icon_label_texts.push_back(props.info.name);
|
||||
|
||||
// Icon image
|
||||
if (props.info.icon != nullptr) {
|
||||
_icon_images.push_back(std::make_unique<Image>(_icon_panels.back()->get()));
|
||||
_icon_images.back()->setSrc(props.info.icon);
|
||||
_icon_images.back()->setAlign(LV_ALIGN_CENTER);
|
||||
uint32_t color = 0xDADADA;
|
||||
if (props.info.userData != nullptr) {
|
||||
color = *(uint32_t*)props.info.userData;
|
||||
}
|
||||
step_colors.push_back(color);
|
||||
|
||||
// Icon image
|
||||
if (props.info.icon != nullptr) {
|
||||
_icon_images.push_back(std::make_unique<Image>(_icon_panels.back()->get()));
|
||||
_icon_images.back()->setSrc(props.info.icon);
|
||||
_icon_images.back()->setAlign(LV_ALIGN_CENTER);
|
||||
}
|
||||
|
||||
icon_x += _icon_gap;
|
||||
}
|
||||
|
||||
icon_x += _icon_gap;
|
||||
}
|
||||
|
||||
/* ------------------------------ LR indicators ----------------------------- */
|
||||
// Scroll to nearby icon handler with wrap-around
|
||||
int total_icons = appPorps.size();
|
||||
auto scroll_to_nearby_icon = [&, total_icons](int direction) {
|
||||
// Scroll to nearby icon handler
|
||||
auto scroll_to_nearby_icon = [&](int direction) {
|
||||
auto current_scroll_x = _panel->getScrollX();
|
||||
int current_index = (current_scroll_x + _icon_gap / 2) / _icon_gap;
|
||||
int target_index = current_index + direction;
|
||||
|
||||
// Wrap around at boundaries
|
||||
if (target_index < 0) {
|
||||
target_index = total_icons - 1;
|
||||
} else if (target_index >= total_icons) {
|
||||
target_index = 0;
|
||||
}
|
||||
|
||||
int target_x = target_index * _icon_gap;
|
||||
int scroll_distance = target_x - current_scroll_x;
|
||||
_panel->scrollBy(-scroll_distance, 0, LV_ANIM_ON);
|
||||
@@ -409,16 +415,6 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
/* ---------------------------- Dynamic bg color ---------------------------- */
|
||||
_dynamic_bg_color = std::make_unique<DynamicBgColor>();
|
||||
|
||||
std::vector<uint32_t> step_colors;
|
||||
step_colors.resize(appPorps.size());
|
||||
for (size_t i = 0; i < appPorps.size(); i++) {
|
||||
uint32_t color = 0xDADADA;
|
||||
if (appPorps[i].info.userData != nullptr) {
|
||||
color = *(uint32_t*)appPorps[i].info.userData;
|
||||
}
|
||||
step_colors[i] = color;
|
||||
}
|
||||
|
||||
_dynamic_bg_color->onBgColorChanged = [&](const uint32_t& bgColor) {
|
||||
// mclog::tagInfo(_tag, "bg color changed to {:06X}", bgColor);
|
||||
_panel->setBgColor(lv_color_hex(bgColor));
|
||||
@@ -428,6 +424,7 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
|
||||
/* ------------------------------ Page indicator ---------------------------- */
|
||||
_page_indicator = std::make_unique<PageIndicator>();
|
||||
// Page indicator only needs to know the real app count (N), not N * copies
|
||||
_page_indicator->init(appPorps.size(), _icon_gap, _panel->get(), 0, 103);
|
||||
|
||||
/* --------------------------- Dynamic icon label --------------------------- */
|
||||
@@ -435,20 +432,51 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
_dynamic_icon_label->init(icon_label_texts, _icon_gap, _panel->get());
|
||||
|
||||
/* ----------------------------- History restore ---------------------------- */
|
||||
bool need_restore = false;
|
||||
int restore_icon_pos_x = -1;
|
||||
|
||||
// Normal start pos (Center of the repeated sets)
|
||||
int base_offset_rounds = _center_copy_index * appPorps.size();
|
||||
int default_start_x = base_offset_rounds * _icon_gap;
|
||||
|
||||
// If warm boot was requested
|
||||
if (GetHAL().getWarmRebootTarget() >= 0) {
|
||||
auto app_index = GetHAL().getWarmRebootTarget();
|
||||
mclog::tagInfo(_tag, "warm boot was requested, app index: {}", app_index);
|
||||
app_index = uitk::clamp(app_index, 0, static_cast<int>(appPorps.size()) - 1);
|
||||
|
||||
// Restore to center set
|
||||
restore_icon_pos_x = (base_offset_rounds + app_index) * _icon_gap;
|
||||
need_restore = true;
|
||||
GetHAL().clearWarmRebootRequest();
|
||||
}
|
||||
|
||||
if (_last_clicked_icon_pos_x != -1) {
|
||||
// Just restore where they left off, it should be in a valid range
|
||||
// mclog::tagInfo(_tag, "navigate to last clicked icon, pos x: {}", _last_clicked_icon_pos_x);
|
||||
_panel->scrollBy(-_last_clicked_icon_pos_x, 0, LV_ANIM_OFF);
|
||||
|
||||
_dynamic_bg_color->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
_page_indicator->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
_dynamic_icon_label->jumpTo(_last_clicked_icon_pos_x / _icon_gap);
|
||||
|
||||
restore_icon_pos_x = _last_clicked_icon_pos_x;
|
||||
need_restore = true;
|
||||
_last_clicked_icon_pos_x = -1;
|
||||
_state = STATE_NORMAL;
|
||||
}
|
||||
|
||||
if (need_restore) {
|
||||
_panel->scrollBy(-restore_icon_pos_x, 0, LV_ANIM_OFF);
|
||||
|
||||
_dynamic_bg_color->jumpTo(restore_icon_pos_x / _icon_gap);
|
||||
_page_indicator->jumpTo(restore_icon_pos_x / _icon_gap);
|
||||
_dynamic_icon_label->jumpTo(restore_icon_pos_x / _icon_gap);
|
||||
|
||||
_state = STATE_NORMAL;
|
||||
}
|
||||
|
||||
// If first create
|
||||
else {
|
||||
// Init at Center Set
|
||||
_panel->scrollBy(-default_start_x, 0, LV_ANIM_OFF);
|
||||
_dynamic_bg_color->jumpTo(default_start_x / _icon_gap);
|
||||
_page_indicator->jumpTo(default_start_x / _icon_gap);
|
||||
_dynamic_icon_label->jumpTo(default_start_x / _icon_gap);
|
||||
|
||||
// Setup startup animation
|
||||
// x for pos_y, y for radius
|
||||
_startup_anim = std::make_unique<AnimateVector2>();
|
||||
@@ -461,11 +489,13 @@ void LauncherView::init(std::vector<mooncake::AppProps_t> appPorps)
|
||||
_startup_anim->teleport(240, 120);
|
||||
_panel->setY(_startup_anim->directValue().x);
|
||||
_panel->setRadius(_startup_anim->directValue().y);
|
||||
|
||||
_startup_anim->move(0, 0);
|
||||
|
||||
_state = STATE_STARTUP;
|
||||
}
|
||||
|
||||
// Destory boot logo label
|
||||
GetHAL().bootLogo.reset();
|
||||
}
|
||||
|
||||
void LauncherView::update()
|
||||
@@ -504,6 +534,43 @@ void LauncherView::handle_state_normal()
|
||||
_clicked_app_id = -1;
|
||||
}
|
||||
|
||||
// We get total size from underlying icons count / copies
|
||||
int total_icons = _icon_panels.size();
|
||||
int icons_per_set = total_icons / _loop_copies;
|
||||
int set_width_px = icons_per_set * _icon_gap;
|
||||
|
||||
// Check boundaries
|
||||
// If we are mostly in Copy 1, jump to Copy 2
|
||||
// If we are mostly in Copy 3, jump to Copy 2
|
||||
// Copy Index: 0 1 [2] 3 4
|
||||
|
||||
int current_scroll_x = _panel->getScrollX();
|
||||
|
||||
// Define safe zone (Copy 2)
|
||||
int center_set_start_x = _center_copy_index * set_width_px;
|
||||
|
||||
// Thresholds: midpoint of Wrap sets
|
||||
int left_trigger_limit = 1 * set_width_px + (set_width_px / 2); // Middle of Set 1
|
||||
int right_trigger_limit = 3 * set_width_px + (set_width_px / 2); // Middle of Set 3
|
||||
|
||||
// Wrap-around Logic
|
||||
// Only perform teleport if we are NOT in an automated scroll animation
|
||||
// (To avoid interrupting the snap/scroll-to animation which would leave us stuck between icons)
|
||||
// However, if the user is manually dragging (PRESSED), we MUST teleport to allow infinite drag.
|
||||
bool is_auto_scrolling = lv_obj_is_scrolling(_panel->get()) && !lv_obj_has_state(_panel->get(), LV_STATE_PRESSED);
|
||||
|
||||
if (!is_auto_scrolling) {
|
||||
if (current_scroll_x < left_trigger_limit) {
|
||||
// Too far left (Set 1), warp right to Set 2
|
||||
// scrollBy(-val) increases scroll_x
|
||||
_panel->scrollBy(-set_width_px, 0, LV_ANIM_OFF);
|
||||
} else if (current_scroll_x > right_trigger_limit) {
|
||||
// Too far right (Set 3), warp left to Set 2
|
||||
// scrollBy(+val) decreases scroll_x
|
||||
_panel->scrollBy(set_width_px, 0, LV_ANIM_OFF);
|
||||
}
|
||||
}
|
||||
|
||||
int scroll_x = _panel->getScrollX();
|
||||
// mclog::tagInfo(_tag, "scroll x: {}", scroll_x);
|
||||
|
||||
|
||||
@@ -36,25 +36,23 @@ void AppSetup::onOpen()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on open");
|
||||
|
||||
LvglLockGuard lock;
|
||||
// Reset state
|
||||
_destroy_menu = false;
|
||||
_need_warm_reset = false;
|
||||
_magic_count = 0;
|
||||
|
||||
_menu_sections = {{
|
||||
"Connectivity",
|
||||
{
|
||||
// {"Set Up Wi-Fi",
|
||||
// [&]() {
|
||||
// _destroy_menu = true;
|
||||
// _worker = std::make_unique<WifiSetupWorker>();
|
||||
// }},
|
||||
{"App Bind Code",
|
||||
{{"Set Up Wi-Fi",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<AppBindCodeWorker>();
|
||||
_destroy_menu = true;
|
||||
_need_warm_reset = true;
|
||||
_worker = std::make_unique<WifiSetupWorker>();
|
||||
}}},
|
||||
},
|
||||
{
|
||||
"Servo",
|
||||
{{"Zero Calibration",
|
||||
{{"Calibration",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<ZeroCalibrationWorker>();
|
||||
@@ -66,15 +64,45 @@ void AppSetup::onOpen()
|
||||
}}},
|
||||
},
|
||||
{
|
||||
"About",
|
||||
{{fmt::format("FW Version: {}", common::FirmwareVersion), nullptr}},
|
||||
"Display",
|
||||
{{"Brightness",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<BrightnessSetupWorker>();
|
||||
}}},
|
||||
},
|
||||
{
|
||||
"End",
|
||||
{{"Quit", [&]() { close(); }}},
|
||||
"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>();
|
||||
}}},
|
||||
}};
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_menu_page = std::make_unique<view::SelectMenuPage>(_menu_sections);
|
||||
|
||||
view::create_home_indicator([&]() { close(); });
|
||||
view::create_status_bar();
|
||||
}
|
||||
|
||||
void AppSetup::onRunning()
|
||||
@@ -99,6 +127,9 @@ void AppSetup::onRunning()
|
||||
}
|
||||
|
||||
GetStackChan().update();
|
||||
|
||||
view::update_home_indicator();
|
||||
view::update_status_bar();
|
||||
}
|
||||
|
||||
void AppSetup::onClose()
|
||||
@@ -107,5 +138,14 @@ void AppSetup::onClose()
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_menu_sections.clear();
|
||||
_menu_page.reset();
|
||||
_worker.reset();
|
||||
|
||||
view::destroy_home_indicator();
|
||||
view::destroy_status_bar();
|
||||
|
||||
if (_need_warm_reset) {
|
||||
GetHAL().requestWarmReboot(3);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,5 +28,8 @@ private:
|
||||
std::vector<view::SelectMenuPage::MenuSection> _menu_sections;
|
||||
std::unique_ptr<view::SelectMenuPage> _menu_page;
|
||||
std::unique_ptr<setup_workers::WorkerBase> _worker;
|
||||
bool _destroy_menu = false;
|
||||
|
||||
bool _destroy_menu = false;
|
||||
bool _need_warm_reset = false;
|
||||
int _magic_count = 0;
|
||||
};
|
||||
|
||||
@@ -12,11 +12,12 @@ SelectMenuPage::SelectMenuPage(std::vector<MenuSection> sections) : _sections(st
|
||||
{
|
||||
_pannel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
|
||||
_pannel->setSize(320, 240);
|
||||
_pannel->setBgColor(lv_color_hex(0xffffff));
|
||||
_pannel->setPadding(10, 24, 0, 0);
|
||||
_pannel->setBgColor(lv_color_hex(0xEDF4FF));
|
||||
_pannel->setPadding(30, 72, 0, 0);
|
||||
_pannel->setBorderWidth(0);
|
||||
_pannel->setRadius(0);
|
||||
_pannel->setScrollDir(LV_DIR_VER);
|
||||
_pannel->setScrollbarMode(LV_SCROLLBAR_MODE_ACTIVE);
|
||||
|
||||
int cursor_y = 10;
|
||||
|
||||
@@ -70,7 +71,7 @@ void SelectMenuPage::create_item_button(int y, const MenuItem& item, int section
|
||||
btn->setBgColor(lv_color_hex(0xB8D3FD));
|
||||
btn->setBorderWidth(0);
|
||||
btn->setShadowWidth(0);
|
||||
btn->setRadius(12);
|
||||
btn->setRadius(18);
|
||||
|
||||
btn->label().setText(item.label);
|
||||
btn->label().setTextFont(&lv_font_montserrat_24);
|
||||
|
||||
@@ -0,0 +1,296 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
// Ref: https://dribbble.com/shots/21953371-WeStud-Creative-Log-In-For-The-Educational-Platform
|
||||
// Idea by cxbbb
|
||||
#include "workers.h"
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
using namespace uitk;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
using namespace setup_workers;
|
||||
|
||||
static std::string _tag = "Setup-About";
|
||||
|
||||
class Egg {
|
||||
public:
|
||||
class Face {
|
||||
public:
|
||||
Face(lv_obj_t* parent, const Vector2i& position, float stiffness, float damping, int bodyHeight)
|
||||
{
|
||||
const Vector2 logo_size = {64, 48};
|
||||
const uint32_t color = 0x36064D;
|
||||
|
||||
_position = position;
|
||||
|
||||
_logo = std::make_unique<Container>(parent);
|
||||
_logo->setSize(logo_size.width, logo_size.height);
|
||||
_logo->align(LV_ALIGN_TOP_LEFT, position.x, position.y);
|
||||
_logo->setBgOpa(LV_OPA_TRANSP);
|
||||
_logo->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_logo->setPaddingAll(0);
|
||||
_logo->setBorderWidth(0);
|
||||
_logo->setRadius(0);
|
||||
|
||||
_left_eye = std::make_unique<Container>(_logo->get());
|
||||
_left_eye->align(LV_ALIGN_CENTER, -15, -3);
|
||||
_left_eye->setBgColor(lv_color_hex(color));
|
||||
_left_eye->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_left_eye->setRadius(LV_RADIUS_CIRCLE);
|
||||
_left_eye->setBorderWidth(0);
|
||||
_left_eye->setSize(6, 6);
|
||||
|
||||
_right_eye = std::make_unique<Container>(_logo->get());
|
||||
_right_eye->align(LV_ALIGN_CENTER, 15, -3);
|
||||
_right_eye->setBgColor(lv_color_hex(color));
|
||||
_right_eye->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_right_eye->setRadius(LV_RADIUS_CIRCLE);
|
||||
_right_eye->setBorderWidth(0);
|
||||
_right_eye->setSize(6, 6);
|
||||
|
||||
_mouth = std::make_unique<Container>(_logo->get());
|
||||
_mouth->align(LV_ALIGN_CENTER, 0, 5);
|
||||
_mouth->setBgColor(lv_color_hex(color));
|
||||
_mouth->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_mouth->setBorderWidth(0);
|
||||
_mouth->setSize(18, 2);
|
||||
_mouth->setRadius(0);
|
||||
|
||||
_look_at_anim.x.springOptions().stiffness = stiffness;
|
||||
_look_at_anim.x.springOptions().damping = damping;
|
||||
_look_at_anim.y.springOptions() = _look_at_anim.x.springOptions();
|
||||
|
||||
_look_at_anim.teleport(_position.x, _position.y + bodyHeight);
|
||||
update();
|
||||
}
|
||||
|
||||
void lookAt(float x, float y, bool instant = false)
|
||||
{
|
||||
// x, y are expected to be in range [-1, 1]
|
||||
const float max_x_offset = 17.0f;
|
||||
const float max_y_offset = 13.0f;
|
||||
|
||||
float target_x = _position.x + x * max_x_offset;
|
||||
float target_y = _position.y + y * max_y_offset;
|
||||
|
||||
if (instant) {
|
||||
_logo->setPos(target_x, target_y);
|
||||
_look_at_anim.teleport(target_x, target_y);
|
||||
} else {
|
||||
_look_at_anim.move(target_x, target_y);
|
||||
}
|
||||
}
|
||||
|
||||
void update()
|
||||
{
|
||||
_look_at_anim.update();
|
||||
if (!_look_at_anim.done()) {
|
||||
_logo->setPos(_look_at_anim.directValue().x, _look_at_anim.directValue().y);
|
||||
}
|
||||
|
||||
// Update blink
|
||||
uint32_t now = GetHAL().millis();
|
||||
if (_next_blink_time == 0) {
|
||||
_next_blink_time = now + Random::getInstance().getInt(100, 3000);
|
||||
}
|
||||
|
||||
if (now > _next_blink_time) {
|
||||
if (_is_blinking) {
|
||||
_is_blinking = false;
|
||||
_left_eye->setHidden(false);
|
||||
_right_eye->setHidden(false);
|
||||
_next_blink_time = now + Random::getInstance().getInt(2000, 6000);
|
||||
} else {
|
||||
_is_blinking = true;
|
||||
_left_eye->setHidden(true);
|
||||
_right_eye->setHidden(true);
|
||||
_next_blink_time = now + Random::getInstance().getInt(60, 200);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Container> _logo;
|
||||
std::unique_ptr<Container> _left_eye;
|
||||
std::unique_ptr<Container> _right_eye;
|
||||
std::unique_ptr<Container> _mouth;
|
||||
Vector2i _position;
|
||||
AnimateVector2 _look_at_anim;
|
||||
uint32_t _next_blink_time = 0;
|
||||
bool _is_blinking = false;
|
||||
};
|
||||
|
||||
class Cube {
|
||||
public:
|
||||
struct MetaData_t {
|
||||
Vector2i position;
|
||||
Vector2i size;
|
||||
uint32_t color = 0;
|
||||
Vector2i facePosition;
|
||||
float stiffness = 200.0f;
|
||||
float damping = 20.0f;
|
||||
};
|
||||
|
||||
Cube(lv_obj_t* parent, const MetaData_t& metaData)
|
||||
{
|
||||
_position = metaData.position;
|
||||
_face_position = metaData.facePosition;
|
||||
|
||||
_body = std::make_unique<Container>(parent);
|
||||
_body->setBgColor(lv_color_hex(metaData.color));
|
||||
_body->align(LV_ALIGN_TOP_LEFT, metaData.position.x, metaData.position.y);
|
||||
_body->setBorderWidth(0);
|
||||
_body->setSize(metaData.size.x, metaData.size.y);
|
||||
_body->setRadius(0);
|
||||
_body->setPaddingAll(0);
|
||||
|
||||
_face = std::make_unique<Face>(parent, metaData.facePosition, metaData.stiffness, metaData.damping,
|
||||
metaData.size.y);
|
||||
|
||||
_body_anim.x.springOptions().stiffness = metaData.stiffness;
|
||||
_body_anim.x.springOptions().damping = metaData.damping;
|
||||
_body_anim.y.springOptions() = _body_anim.x.springOptions();
|
||||
|
||||
_body_anim.teleport(_position.x, _position.y + metaData.size.y);
|
||||
moveTo(1, 0);
|
||||
}
|
||||
|
||||
void update(int tpX, int tpY)
|
||||
{
|
||||
auto target = getTiltTarget(tpX, tpY);
|
||||
|
||||
// Update face anim
|
||||
_face->lookAt(target.x, target.y);
|
||||
_face->update();
|
||||
|
||||
// Update body anim
|
||||
moveTo(target.x, target.y);
|
||||
_body_anim.update();
|
||||
if (!_body_anim.done()) {
|
||||
_body->setPos(_body_anim.directValue().x, _body_anim.directValue().y);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 getTiltTarget(int tpX, int tpY)
|
||||
{
|
||||
Vector2 position;
|
||||
|
||||
if (tpX < 0 || tpY < 0) {
|
||||
position.x = 1.0f;
|
||||
position.y = 0.0f;
|
||||
} else {
|
||||
// Face center
|
||||
float cx = _face_position.x + 32.0f;
|
||||
float cy = _face_position.y + 24.0f;
|
||||
|
||||
float x = (tpX - cx) / 160.0f;
|
||||
float y = (tpY - cy) / 120.0f;
|
||||
|
||||
position.x = uitk::clamp(x, -1.0f, 1.0f);
|
||||
position.y = uitk::clamp(y, -1.0f, 1.0f);
|
||||
}
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
void moveTo(float x, float y, bool instant = false)
|
||||
{
|
||||
// x, y are expected to be in range [-1, 1]
|
||||
const float max_x_offset = 5.0f;
|
||||
const float max_y_offset = 10.0f;
|
||||
|
||||
float target_x = _position.x + x * max_x_offset;
|
||||
float target_y = _position.y + y * max_y_offset;
|
||||
|
||||
if (instant) {
|
||||
_body->setPos(target_x, target_y);
|
||||
_body_anim.teleport(target_x, target_y);
|
||||
} else {
|
||||
_body_anim.move(target_x, target_y);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Container> _body;
|
||||
std::unique_ptr<Face> _face;
|
||||
Vector2i _position;
|
||||
Vector2i _face_position;
|
||||
AnimateVector2 _body_anim;
|
||||
};
|
||||
|
||||
inline static const std::vector<Cube::MetaData_t> CubeDefines = {
|
||||
{{54, 55}, {121, 185 + 10}, 0xBE70E5, {83, 55}, 50.0f, 14.0f}, //
|
||||
{{107, 131}, {103, 109 + 10}, 0xDA4848, {133, 131}, 100.0f, 16.0f}, //
|
||||
{{25, 166}, {89, 74 + 10}, 0x76D2DB, {37, 164}, 200.0f, 28.0f}, //
|
||||
{{136, 199}, {121, 42 + 10}, 0xF5AA79, {178, 196}, 25.0f, 10.0f}, //
|
||||
};
|
||||
|
||||
void init()
|
||||
{
|
||||
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());
|
||||
_panel->setBgColor(lv_color_hex(0xF7F6E5));
|
||||
_panel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
_panel->setBorderWidth(0);
|
||||
_panel->setSize(320, 240);
|
||||
_panel->setRadius(0);
|
||||
_panel->setPaddingAll(0);
|
||||
_panel->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
for (const auto& metaData : CubeDefines) {
|
||||
_cubes.push_back(std::make_unique<Cube>(_panel->get(), metaData));
|
||||
}
|
||||
}
|
||||
|
||||
void update()
|
||||
{
|
||||
int tpX = -1;
|
||||
int tpY = -1;
|
||||
|
||||
lv_indev_t* indev = GetHAL().lvTouchpad;
|
||||
if (indev) {
|
||||
lv_indev_state_t state = lv_indev_get_state(indev);
|
||||
if (state == LV_INDEV_STATE_PR) {
|
||||
lv_point_t curr_point;
|
||||
lv_indev_get_point(indev, &curr_point);
|
||||
tpX = curr_point.x;
|
||||
tpY = curr_point.y;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& cube : _cubes) {
|
||||
cube->update(tpX, tpY);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<Container> _panel;
|
||||
std::vector<std::unique_ptr<Cube>> _cubes;
|
||||
};
|
||||
std::unique_ptr<Egg> _egg;
|
||||
|
||||
FwVersionWorker::FwVersionWorker()
|
||||
{
|
||||
_egg = std::make_unique<Egg>();
|
||||
_egg->init();
|
||||
}
|
||||
|
||||
FwVersionWorker::~FwVersionWorker()
|
||||
{
|
||||
_egg.reset();
|
||||
}
|
||||
|
||||
void FwVersionWorker::update()
|
||||
{
|
||||
if (GetHAL().millis() - _last_tick > 16) {
|
||||
_last_tick = GetHAL().millis();
|
||||
_egg->update();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,8 @@
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "workers.h"
|
||||
#include <src/misc/lv_area.h>
|
||||
#include <src/misc/lv_text.h>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <ArduinoJson.hpp>
|
||||
#include <mooncake_log.h>
|
||||
@@ -25,6 +27,9 @@ WifiSetupWorker::WifiSetupWorker()
|
||||
// Create default avatar
|
||||
auto avatar = std::make_unique<avatar::DefaultAvatar>();
|
||||
avatar->init(lv_screen_active(), &lv_font_montserrat_24);
|
||||
avatar->leftEye().setVisible(false);
|
||||
avatar->rightEye().setVisible(false);
|
||||
avatar->mouth().setVisible(false);
|
||||
GetStackChan().attachAvatar(std::move(avatar));
|
||||
|
||||
_app_config_signal_id =
|
||||
@@ -52,31 +57,45 @@ void WifiSetupWorker::update_state()
|
||||
if (_is_first_in) {
|
||||
_is_first_in = false;
|
||||
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.leftEye().setVisible(false);
|
||||
avatar.rightEye().setVisible(false);
|
||||
avatar.mouth().setVisible(false);
|
||||
avatar.setSpeech("Scan the QR code to download the \"StackChan\" app.");
|
||||
|
||||
auto& data = _state_app_download_data;
|
||||
|
||||
std::string qrcode_text = "todotodotodotodotodotodotodlotodotodotodoto";
|
||||
data.panel = std::make_unique<Container>(lv_screen_active());
|
||||
data.panel->setBgColor(lv_color_hex(0xEDF4FF));
|
||||
data.panel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
data.panel->setBorderWidth(0);
|
||||
data.panel->setSize(320, 240);
|
||||
data.panel->setRadius(0);
|
||||
|
||||
data.title = std::make_unique<Label>(lv_screen_active());
|
||||
data.title->setTextFont(&lv_font_montserrat_20);
|
||||
data.title->setTextColor(lv_color_hex(0x7E7B9C));
|
||||
data.title->align(LV_ALIGN_TOP_MID, 0, 13);
|
||||
data.title->setText("APP SETUP");
|
||||
|
||||
std::string qrcode_text = "https://apps.apple.com/us/app/stackchan-world/id6756086326";
|
||||
data.qrcode = std::make_unique<Qrcode>(lv_screen_active());
|
||||
data.qrcode->setSize(150);
|
||||
data.qrcode->setDarkColor(lv_color_white());
|
||||
data.qrcode->setLightColor(lv_color_black());
|
||||
data.qrcode->setDarkColor(lv_color_hex(0x221C5B));
|
||||
data.qrcode->setLightColor(lv_color_hex(0xEDF4FF));
|
||||
data.qrcode->update(qrcode_text);
|
||||
data.qrcode->align(LV_ALIGN_CENTER, -60, -25);
|
||||
data.qrcode->align(LV_ALIGN_CENTER, -72, 12);
|
||||
|
||||
data.btn = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*data.btn);
|
||||
data.btn->align(LV_ALIGN_CENTER, 90, 0);
|
||||
data.btn->setSize(100, 60);
|
||||
data.btn->label().setText("Next");
|
||||
data.btn->onClick().connect([this]() { _state_app_download_data.is_clicked = true; });
|
||||
data.btn_next = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*data.btn_next);
|
||||
data.btn_next->align(LV_ALIGN_CENTER, 79, 73);
|
||||
data.btn_next->setSize(112, 48);
|
||||
data.btn_next->label().setText("Next");
|
||||
data.btn_next->onClick().connect([this]() { _state_app_download_data.next_clicked = true; });
|
||||
|
||||
data.info = std::make_unique<Label>(lv_screen_active());
|
||||
data.info->setTextFont(&lv_font_montserrat_20);
|
||||
data.info->setTextColor(lv_color_hex(0x26206A));
|
||||
data.info->align(LV_ALIGN_TOP_LEFT, 183, 56);
|
||||
data.info->setTextAlign(LV_TEXT_ALIGN_LEFT);
|
||||
data.info->setText("Download\n\"StackChan\"\napp to start\nthe setup");
|
||||
}
|
||||
|
||||
if (_state_app_download_data.is_clicked) {
|
||||
if (_state_app_download_data.next_clicked) {
|
||||
switch_state(State::WaitAppConnection);
|
||||
}
|
||||
|
||||
@@ -94,24 +113,36 @@ void WifiSetupWorker::update_state()
|
||||
if (_is_first_in) {
|
||||
_is_first_in = false;
|
||||
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.leftEye().setVisible(false);
|
||||
avatar.rightEye().setVisible(false);
|
||||
avatar.mouth().setVisible(false);
|
||||
avatar.setSpeech("Look for me in \"StackChan\" app to start the setup.");
|
||||
avatar.addDecorator(std::make_unique<avatar::HeartDecorator>(lv_screen_active(), 3000));
|
||||
auto& data = _state_wait_app_connection_data;
|
||||
|
||||
auto& data = _state_wait_app_connection_data;
|
||||
data.id_btn = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*data.id_btn);
|
||||
data.id_btn->align(LV_ALIGN_CENTER, 0, 0);
|
||||
data.id_btn->setSize(262, 52);
|
||||
data.id_btn->onClick().connect([]() {
|
||||
data.panel = std::make_unique<Container>(lv_screen_active());
|
||||
data.panel->setBgColor(lv_color_hex(0xEDF4FF));
|
||||
data.panel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
data.panel->setBorderWidth(0);
|
||||
data.panel->setSize(320, 240);
|
||||
data.panel->setRadius(0);
|
||||
|
||||
data.btn_id = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*data.btn_id);
|
||||
data.btn_id->align(LV_ALIGN_CENTER, 0, -20);
|
||||
data.btn_id->setSize(262, 52);
|
||||
data.btn_id->onClick().connect([]() {
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.clearDecorators();
|
||||
avatar.addDecorator(std::make_unique<avatar::HeartDecorator>(lv_screen_active(), 3000));
|
||||
});
|
||||
data.id_btn->label().setText(fmt::format("ID: {}", GetHAL().getFactoryMacString()));
|
||||
data.btn_id->label().setText(fmt::format("ID: {}", GetHAL().getFactoryMacString()));
|
||||
|
||||
data.info = std::make_unique<Label>(lv_screen_active());
|
||||
data.info->setTextFont(&lv_font_montserrat_24);
|
||||
data.info->setTextColor(lv_color_hex(0x26206A));
|
||||
data.info->align(LV_ALIGN_BOTTOM_MID, 0, -26);
|
||||
data.info->setTextAlign(LV_TEXT_ALIGN_CENTER);
|
||||
data.info->setText("Look for me in the app\nto start setup.");
|
||||
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.clearDecorators();
|
||||
avatar.addDecorator(std::make_unique<avatar::HeartDecorator>(lv_screen_active(), 3000));
|
||||
}
|
||||
|
||||
// Check events
|
||||
@@ -205,12 +236,10 @@ void WifiSetupWorker::cleanup_ui()
|
||||
switch (_last_state) {
|
||||
case State::AppDownload: {
|
||||
_state_app_download_data.reset();
|
||||
GetStackChan().avatar().setSpeech("");
|
||||
break;
|
||||
}
|
||||
case State::WaitAppConnection: {
|
||||
_state_wait_app_connection_data.reset();
|
||||
GetStackChan().avatar().setSpeech("");
|
||||
break;
|
||||
}
|
||||
case State::AppConnected: {
|
||||
@@ -234,40 +263,3 @@ void WifiSetupWorker::switch_state(State newState)
|
||||
_state = newState;
|
||||
_is_first_in = true;
|
||||
}
|
||||
|
||||
AppBindCodeWorker::AppBindCodeWorker()
|
||||
{
|
||||
// Create default avatar
|
||||
auto avatar = std::make_unique<avatar::DefaultAvatar>();
|
||||
avatar->init(lv_screen_active(), &lv_font_montserrat_24);
|
||||
avatar->leftEye().setVisible(false);
|
||||
avatar->rightEye().setVisible(false);
|
||||
avatar->mouth().setVisible(false);
|
||||
avatar->setSpeech("Scan the QR code with the StackChan APP to bind.");
|
||||
GetStackChan().attachAvatar(std::move(avatar));
|
||||
|
||||
std::string qrcode_text;
|
||||
ArduinoJson::JsonDocument doc;
|
||||
doc["mac"] = GetHAL().getFactoryMacString();
|
||||
ArduinoJson::serializeJson(doc, qrcode_text);
|
||||
mclog::tagInfo(_tag, "qr code text: {}", qrcode_text);
|
||||
|
||||
_qrcode = std::make_unique<Qrcode>(lv_screen_active());
|
||||
_qrcode->setSize(150);
|
||||
_qrcode->setDarkColor(lv_color_white());
|
||||
_qrcode->setLightColor(lv_color_black());
|
||||
_qrcode->update(qrcode_text);
|
||||
_qrcode->align(LV_ALIGN_CENTER, -60, -25);
|
||||
|
||||
_btn_quit = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*_btn_quit);
|
||||
_btn_quit->align(LV_ALIGN_CENTER, 90, 0);
|
||||
_btn_quit->setSize(100, 60);
|
||||
_btn_quit->label().setText("Back");
|
||||
_btn_quit->onClick().connect([this]() { _is_done = true; });
|
||||
}
|
||||
|
||||
AppBindCodeWorker::~AppBindCodeWorker()
|
||||
{
|
||||
GetStackChan().resetAvatar();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "workers.h"
|
||||
#include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
using namespace smooth_ui_toolkit::lvgl_cpp;
|
||||
using namespace setup_workers;
|
||||
|
||||
static std::string _tag = "Setup-Display";
|
||||
static std::vector<uint8_t> _brightness_levels = {1, 15, 30, 45, 60, 75, 90, 100};
|
||||
|
||||
BrightnessSetupWorker::BrightnessSetupWorker()
|
||||
{
|
||||
mclog::info("BrightnessSetupWorker start");
|
||||
|
||||
uint8_t current_brightness = GetHAL().getBackLightBrightness();
|
||||
int current_index = 0;
|
||||
for (size_t i = 0; i < _brightness_levels.size(); i++) {
|
||||
if (_brightness_levels[i] >= current_brightness) {
|
||||
current_index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_panel = std::make_unique<Container>(lv_screen_active());
|
||||
_panel->setBgColor(lv_color_hex(0xEDF4FF));
|
||||
_panel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
_panel->setBorderWidth(0);
|
||||
_panel->setSize(320, 240);
|
||||
_panel->setRadius(0);
|
||||
_panel->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
|
||||
_label_brightness = std::make_unique<Label>(*_panel);
|
||||
_label_brightness->setText(fmt::format("{}%", _brightness_levels[current_index]));
|
||||
_label_brightness->setTextFont(&lv_font_montserrat_24);
|
||||
_label_brightness->setTextColor(lv_color_hex(0x26206A));
|
||||
_label_brightness->align(LV_ALIGN_CENTER, 0, -70);
|
||||
|
||||
_slider = std::make_unique<Slider>(*_panel);
|
||||
_slider->align(LV_ALIGN_CENTER, 0, -12);
|
||||
_slider->setRange(0, _brightness_levels.size() - 1);
|
||||
_slider->setSize(250, 18);
|
||||
_slider->setBgColor(lv_color_hex(0x615B9E), LV_PART_KNOB);
|
||||
_slider->setBgColor(lv_color_hex(0x615B9E), LV_PART_INDICATOR);
|
||||
_slider->setBgColor(lv_color_hex(0xB8D3FD), LV_PART_MAIN);
|
||||
_slider->setBgOpa(255);
|
||||
_slider->setValue(current_index);
|
||||
_slider->onValueChanged().connect([this](int32_t value) {
|
||||
_label_brightness->setText(fmt::format("{}%", _brightness_levels[value]));
|
||||
_target_brightness = _brightness_levels[value];
|
||||
});
|
||||
|
||||
_btn_confirm = std::make_unique<Button>(*_panel);
|
||||
apply_button_common_style(*_btn_confirm);
|
||||
_btn_confirm->align(LV_ALIGN_CENTER, 0, 60);
|
||||
_btn_confirm->setSize(150, 50);
|
||||
_btn_confirm->label().setText("Confirm");
|
||||
_btn_confirm->onClick().connect([this]() { _is_done = true; });
|
||||
}
|
||||
|
||||
void BrightnessSetupWorker::update()
|
||||
{
|
||||
if (_target_brightness != -1) {
|
||||
GetHAL().setBackLightBrightness(_target_brightness, false);
|
||||
_target_brightness = -1;
|
||||
}
|
||||
}
|
||||
|
||||
BrightnessSetupWorker::~BrightnessSetupWorker()
|
||||
{
|
||||
auto brightness = _brightness_levels[_slider->getValue()];
|
||||
mclog::tagInfo(_tag, "final brightness: {}", brightness);
|
||||
GetHAL().setBackLightBrightness(brightness, true);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user