/* * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ #include "app_dance.h" #include #include #include #include #include #include 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 loading_page; { LvglLockGuard lock; loading_page = std::make_unique(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->init(lv_screen_active()); GetStackChan().attachAvatar(std::move(avatar)); /* ------------------------------- BLE events ------------------------------- */ GetHAL().onBleAvatarData.connect([&](const char* data) { std::lock_guard 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 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 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 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(); }