update firmware v1.2.4 (#16)

This commit is contained in:
Forairaaaaa
2026-04-20 16:27:36 +08:00
committed by GitHub
parent 605b575fcc
commit dd34f9e0ec
94 changed files with 3615 additions and 41513 deletions
@@ -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()
{
}
@@ -0,0 +1,153 @@
/*
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
*
* SPDX-License-Identifier: MIT
*/
#include "workers.h"
#include <hal/hal.h>
#include <mooncake_log.h>
#include <apps/common/loading_page/loading_page.h>
using namespace uitk::lvgl_cpp;
using namespace setup_workers;
static std::string _tag = "Setup-Account";
AccountWorker::PanelInfo::PanelInfo(lv_obj_t* parent, int posY, std::string_view title, std::string_view info)
{
_panel = std::make_unique<Container>(parent);
_panel->setBgColor(lv_color_hex(0xB8D3FD));
_panel->align(LV_ALIGN_TOP_MID, 0, posY);
_panel->setBorderWidth(0);
_panel->setSize(296, 108);
_panel->setPadding(0, 0, 0, 0);
_panel->setRadius(18);
_panel->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
_label_title = std::make_unique<Label>(_panel->get());
_label_title->setTextFont(&lv_font_montserrat_20);
_label_title->setTextColor(lv_color_hex(0x445B80));
_label_title->align(LV_ALIGN_TOP_MID, 0, 13);
_label_title->setText(title);
_label_info = std::make_unique<Label>(_panel->get());
_label_info->setTextFont(&lv_font_montserrat_20);
_label_info->setTextColor(lv_color_hex(0x07162C));
_label_info->align(LV_ALIGN_CENTER, 0, 14);
_label_info->setWidth(270);
_label_info->setTextAlign(LV_TEXT_ALIGN_CENTER);
_label_info->setText(info);
}
AccountWorker::PageAccount::PageAccount(std::string_view username, std::string_view deviceName)
{
_panel = std::make_unique<Container>(lv_screen_active());
_panel->setBgColor(lv_color_hex(0xF6F6F6));
_panel->align(LV_ALIGN_CENTER, 0, 0);
_panel->setBorderWidth(0);
_panel->setSize(320, 240);
_panel->setPadding(0, 20, 0, 0);
_panel->setRadius(0);
// Title
_label_title = std::make_unique<Label>(_panel->get());
_label_title->setTextFont(&lv_font_montserrat_20);
_label_title->setTextColor(lv_color_hex(0x7E7B9C));
_label_title->align(LV_ALIGN_TOP_MID, 0, 12);
_label_title->setText("ACCOUNT");
_panel_username = std::make_unique<PanelInfo>(_panel->get(), 50, "M5Stack Account:", username);
_panel_device_name = std::make_unique<PanelInfo>(_panel->get(), 174, "Device Name:", deviceName);
// Button
_btn_unbind = std::make_unique<Button>(_panel->get());
apply_button_common_style(*_btn_unbind);
_btn_unbind->align(LV_ALIGN_TOP_MID, 0, 303);
_btn_unbind->setSize(290, 48);
_btn_unbind->setBgColor(lv_color_hex(0xFF8080));
_btn_unbind->label().setText("Unbind and factory reset");
_btn_unbind->label().setTextFont(&lv_font_montserrat_20);
_btn_unbind->label().setTextColor(lv_color_hex(0x731F1F));
_btn_unbind->onClick().connect([this]() { _is_unbind_clicked = true; });
_btn_quit = std::make_unique<Button>(_panel->get());
apply_button_common_style(*_btn_quit);
_btn_quit->align(LV_ALIGN_TOP_MID, 0, 371);
_btn_quit->setSize(290, 48);
_btn_quit->label().setText("Back");
_btn_quit->label().setTextFont(&lv_font_montserrat_20);
_btn_quit->onClick().connect([this]() { _is_quit_clicked = true; });
}
AccountWorker::AccountWorker()
{
// Update account info
{
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 info
bool result = GetHAL().updateAccountInfo([&](std::string_view msg) {
LvglLockGuard lock;
loading_page->setMessage(msg);
});
if (!result) {
GetHAL().delay(5000);
}
GetHAL().lvglLock();
}
auto info = GetHAL().getUserAccountInfo();
_page_account = std::make_unique<PageAccount>(info.username, info.deviceName);
}
AccountWorker::~AccountWorker()
{
}
void AccountWorker::update()
{
if (_page_account) {
if (_page_account->isUnbindClicked()) {
mclog::tagInfo(_tag, "unbind clicked");
_page_account.reset();
// Unbind account
{
auto loading_page = std::make_unique<view::LoadingPage>(0xF6F6F6, 0x26206A);
GetHAL().lvglUnlock();
bool result = GetHAL().unbindAccount([&](std::string_view msg) {
LvglLockGuard lock;
loading_page->setMessage(msg);
});
if (!result) {
GetHAL().delay(5000);
}
GetHAL().lvglLock();
}
// Factory reset
_worker_reset = std::make_unique<FactoryResetWorker>();
} else if (_page_account->isQuitClicked()) {
mclog::tagInfo(_tag, "quit clicked");
_is_done = true;
}
} else if (_worker_reset) {
_worker_reset->update();
if (_worker_reset->isDone()) {
_worker_reset.reset();
_is_done = true;
}
}
}
@@ -31,11 +31,6 @@ WifiSetupWorker::WifiSetupWorker()
avatar->rightEye().setVisible(false);
avatar->mouth().setVisible(false);
GetStackChan().attachAvatar(std::move(avatar));
_app_config_signal_id =
GetHAL().onAppConfigEvent.connect([this](AppConfigEvent event) { _last_app_config_event = event; });
GetHAL().startAppConfigServer();
}
WifiSetupWorker::~WifiSetupWorker()
@@ -69,30 +64,65 @@ void WifiSetupWorker::update_state()
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->align(LV_ALIGN_TOP_MID, 0, 0);
data.title->setText("APP SETUP");
data.info = std::make_unique<Label>(lv_screen_active());
data.info->setTextFont(&lv_font_montserrat_14);
data.info->setTextColor(lv_color_hex(0x26206A));
data.info->align(LV_ALIGN_TOP_MID, 0, 27);
data.info->setTextAlign(LV_TEXT_ALIGN_CENTER);
data.info->setText("Install \"StackChan World\" app\nand login to your M5Stack account");
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_hex(0x221C5B));
data.qrcode->setLightColor(lv_color_hex(0xEDF4FF));
data.qrcode->update(qrcode_text);
data.qrcode->align(LV_ALIGN_CENTER, -72, 12);
data.qrcode_ios = std::make_unique<Qrcode>(lv_screen_active());
data.qrcode_ios->setSize(80);
data.qrcode_ios->setDarkColor(lv_color_hex(0x221C5B));
data.qrcode_ios->setLightColor(lv_color_hex(0xEDF4FF));
data.qrcode_ios->update(qrcode_text);
data.qrcode_ios->align(LV_ALIGN_CENTER, -65, -12);
qrcode_text = "https://play.google.com/store/apps/details?id=com.m5stack.stackchan";
data.qrcode_android = std::make_unique<Qrcode>(lv_screen_active());
data.qrcode_android->setSize(80);
data.qrcode_android->setDarkColor(lv_color_hex(0x221C5B));
data.qrcode_android->setLightColor(lv_color_hex(0xEDF4FF));
data.qrcode_android->update(qrcode_text);
data.qrcode_android->align(LV_ALIGN_CENTER, 65, -12);
data.label_ios = std::make_unique<Label>(lv_screen_active());
data.label_ios->setTextFont(&lv_font_montserrat_14);
data.label_ios->setTextColor(lv_color_hex(0x26206A));
data.label_ios->align(LV_ALIGN_CENTER, -65, 47);
data.label_ios->setText("App Store\n(iOS)");
data.label_ios->setTextAlign(LV_TEXT_ALIGN_CENTER);
data.label_android = std::make_unique<Label>(lv_screen_active());
data.label_android->setTextFont(&lv_font_montserrat_14);
data.label_android->setTextColor(lv_color_hex(0x26206A));
data.label_android->align(LV_ALIGN_CENTER, 65, 47);
data.label_android->setText("Play Store\n(Android)");
data.label_android->setTextAlign(LV_TEXT_ALIGN_CENTER);
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->align(LV_ALIGN_CENTER, 72, 91);
data.btn_next->setSize(112, 42);
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");
data.btn_quit = std::make_unique<Button>(lv_screen_active());
apply_button_common_style(*data.btn_quit);
data.btn_quit->align(LV_ALIGN_CENTER, -72, 91);
data.btn_quit->setSize(112, 42);
data.btn_quit->setBgColor(lv_color_hex(0xD4D9E0));
data.btn_quit->label().setText("Back");
data.btn_quit->label().setTextColor(lv_color_hex(0x525064));
data.btn_quit->onClick().connect([this]() { _state_app_download_data.quit_clicked = true; });
}
if (_state_app_download_data.quit_clicked) {
_is_done = true;
}
if (_state_app_download_data.next_clicked) {
@@ -113,6 +143,12 @@ void WifiSetupWorker::update_state()
if (_is_first_in) {
_is_first_in = false;
// Start app config server
_app_config_signal_id =
GetHAL().onAppConfigEvent.connect([this](AppConfigEvent event) { _last_app_config_event = event; });
GetHAL().startAppConfigServer();
auto& data = _state_wait_app_connection_data;
data.panel = std::make_unique<Container>(lv_screen_active());
+269 -3
View File
@@ -9,6 +9,7 @@
#include <mooncake_log.h>
#include <assets/assets.h>
#include <hal/hal.h>
#include <array>
using namespace smooth_ui_toolkit::lvgl_cpp;
using namespace setup_workers;
@@ -36,8 +37,9 @@ public:
_title->align(LV_ALIGN_TOP_MID, 0, 13);
_title->setText("HOME POSITION:");
_img = std::make_unique<Image>(lv_screen_active());
_img->setSrc(&setup_stackchan_front_view);
_img = std::make_unique<Image>(lv_screen_active());
_img_setup_stackchan_front_view = assets::get_image("setup_stackchan_front_view.bin");
_img->setSrc(&_img_setup_stackchan_front_view);
_img->align(LV_ALIGN_CENTER, -74, 15);
_btn_next = std::make_unique<Button>(lv_screen_active());
@@ -62,6 +64,7 @@ private:
std::unique_ptr<uitk::lvgl_cpp::Image> _img;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_next;
std::unique_ptr<uitk::lvgl_cpp::Label> _info;
lv_image_dsc_t _img_setup_stackchan_front_view;
};
/**
@@ -190,6 +193,269 @@ void ZeroCalibrationWorker::update()
}
}
class PageServoTips : public WorkerBase {
public:
PageServoTips()
{
_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);
_title = std::make_unique<Label>(lv_screen_active());
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x7E7B9C));
_title->align(LV_ALIGN_TOP_MID, 0, 13);
_title->setText("SERVO TEST");
_info = std::make_unique<Label>(lv_screen_active());
_info->setWidth(280);
_info->setTextFont(&lv_font_montserrat_16);
_info->setTextColor(lv_color_hex(0x26206A));
_info->align(LV_ALIGN_CENTER, 0, -18);
_info->setTextAlign(LV_TEXT_ALIGN_CENTER);
_info->setText(
"Put me on a flat stable surface\nand remove your hands.\n\nPower me via the bottom USB-C \nif needed. ");
_btn_skip = std::make_unique<Button>(lv_screen_active());
apply_button_common_style(*_btn_skip);
_btn_skip->align(LV_ALIGN_CENTER, -72, 72);
_btn_skip->setSize(112, 48);
_btn_skip->setBgColor(lv_color_hex(0xD4D9E0));
_btn_skip->label().setText("Skip");
_btn_skip->label().setTextFont(&lv_font_montserrat_20);
_btn_skip->label().setTextColor(lv_color_hex(0x525064));
_btn_skip->onClick().connect([this]() { _is_skip_clicked = true; });
_btn_start = std::make_unique<Button>(lv_screen_active());
apply_button_common_style(*_btn_start);
_btn_start->align(LV_ALIGN_CENTER, 72, 72);
_btn_start->setSize(112, 48);
_btn_start->label().setText("Start");
_btn_start->label().setTextFont(&lv_font_montserrat_20);
_btn_start->onClick().connect([this]() { _is_start_clicked = true; });
}
bool isSkipClicked() const
{
return _is_skip_clicked;
}
bool isStartClicked() const
{
return _is_start_clicked;
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::unique_ptr<uitk::lvgl_cpp::Label> _info;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_skip;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_start;
bool _is_skip_clicked = false;
bool _is_start_clicked = false;
};
class PageServoTest : public WorkerBase {
public:
PageServoTest()
{
_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);
_title = std::make_unique<Label>(lv_screen_active());
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x7E7B9C));
_title->align(LV_ALIGN_TOP_MID, 0, 13);
_title->setText("SERVO TEST");
_info = std::make_unique<Label>(lv_screen_active());
_info->setWidth(280);
_info->setTextFont(&lv_font_montserrat_20);
_info->setTextColor(lv_color_hex(0x26206A));
_info->align(LV_ALIGN_CENTER, 0, -12);
_info->setTextAlign(LV_TEXT_ALIGN_CENTER);
_info->setText("Preparing...");
auto& motion = GetStackChan().motion();
motion.setAutoAngleSyncEnabled(true);
}
void update() override
{
if (_current_step >= _steps.size()) {
_is_done = true;
return;
}
const auto now = GetHAL().millis();
if (!_step_started) {
run_step(_steps[_current_step]);
_step_started = true;
_step_start_tick = now;
return;
}
if (now - _step_start_tick >= _step_delay_ms) {
_current_step++;
_step_started = false;
}
}
private:
enum class Step {
GoHome1,
Left90,
GoHome2,
Right90,
GoHome3,
Up90,
GoHome4,
};
void run_step(Step step)
{
auto& motion = GetStackChan().motion();
switch (step) {
case Step::GoHome1:
case Step::GoHome2:
case Step::GoHome3:
case Step::GoHome4:
_info->setText("Returning to\nthe home position...");
motion.goHome(_move_speed);
break;
case Step::Left90:
_info->setText("Moving left...");
motion.moveWithSpeed(900, 0, _move_speed);
break;
case Step::Right90:
_info->setText("Moving right...");
motion.moveWithSpeed(-900, 0, _move_speed);
break;
case Step::Up90:
_info->setText("Moving up...");
motion.moveWithSpeed(0, 900, _move_speed);
break;
}
}
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::unique_ptr<uitk::lvgl_cpp::Label> _info;
static constexpr uint32_t _step_delay_ms = 1800;
static constexpr int _move_speed = 500;
const std::array<Step, 7> _steps = {
Step::GoHome1, Step::Left90, Step::GoHome2, Step::Right90, Step::GoHome3, Step::Up90, Step::GoHome4,
};
size_t _current_step = 0;
uint32_t _step_start_tick = 0;
bool _step_started = false;
};
class PageServoDone : public WorkerBase {
public:
PageServoDone()
{
_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);
_title = std::make_unique<Label>(lv_screen_active());
_title->setTextFont(&lv_font_montserrat_20);
_title->setTextColor(lv_color_hex(0x7E7B9C));
_title->align(LV_ALIGN_TOP_MID, 0, 13);
_title->setText("SERVO TEST");
_info = std::make_unique<Label>(lv_screen_active());
_info->setWidth(280);
_info->setTextFont(&lv_font_montserrat_20);
_info->setTextColor(lv_color_hex(0x26206A));
_info->align(LV_ALIGN_CENTER, 0, -16);
_info->setTextAlign(LV_TEXT_ALIGN_CENTER);
_info->setText("Servo test completed.\n");
_btn_retest = std::make_unique<Button>(lv_screen_active());
apply_button_common_style(*_btn_retest);
_btn_retest->align(LV_ALIGN_CENTER, -72, 67);
_btn_retest->setSize(112, 48);
_btn_retest->setBgColor(lv_color_hex(0xD4D9E0));
_btn_retest->label().setText("Retest");
_btn_retest->label().setTextFont(&lv_font_montserrat_20);
_btn_retest->label().setTextColor(lv_color_hex(0x525064));
_btn_retest->onClick().connect([this]() { _is_retest_clicked = true; });
_btn_next = std::make_unique<Button>(lv_screen_active());
apply_button_common_style(*_btn_next);
_btn_next->align(LV_ALIGN_CENTER, 72, 67);
_btn_next->setSize(112, 48);
_btn_next->label().setText("Next");
_btn_next->label().setTextFont(&lv_font_montserrat_20);
_btn_next->onClick().connect([this]() { _is_next_clicked = true; });
}
bool isRetestClicked() const
{
return _is_retest_clicked;
}
bool isNextClicked() const
{
return _is_next_clicked;
}
private:
std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Label> _title;
std::unique_ptr<uitk::lvgl_cpp::Label> _info;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_retest;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_next;
bool _is_retest_clicked = false;
bool _is_next_clicked = false;
};
ServoTestWorker::ServoTestWorker()
{
_page_tips = std::make_unique<PageServoTips>();
}
void ServoTestWorker::update()
{
if (_page_tips) {
auto* page = static_cast<PageServoTips*>(_page_tips.get());
if (page->isSkipClicked()) {
_page_tips.reset();
_is_done = true;
} else if (page->isStartClicked()) {
_page_tips.reset();
_page_test = std::make_unique<PageServoTest>();
}
} else if (_page_test) {
_page_test->update();
if (_page_test->isDone()) {
_page_test.reset();
_page_done = std::make_unique<PageServoDone>();
}
} else if (_page_done) {
auto* page = static_cast<PageServoDone*>(_page_done.get());
if (page->isRetestClicked()) {
_page_done.reset();
_page_test = std::make_unique<PageServoTest>();
} else if (page->isNextClicked()) {
_page_done.reset();
_is_done = true;
}
}
}
struct RgbColorEntry {
std::string name;
uint8_t r;
@@ -235,7 +501,7 @@ RgbTestWorker::RgbTestWorker()
auto btn_quit = std::make_unique<Button>(*_panel);
apply_button_common_style(*btn_quit);
btn_quit->setSize(200, 50);
btn_quit->label().setText("Quit");
btn_quit->label().setText("Back");
btn_quit->onClick().connect([this]() { _is_done = true; });
_buttons.push_back(std::move(btn_quit));
}
@@ -67,6 +67,15 @@ void StartupWorker::update()
_is_done = true;
} else if (_page_startup->isStartClicked()) {
_page_startup.reset();
mclog::tagInfo(_tag, "start servo test");
_worker_servo_test = std::make_unique<ServoTestWorker>();
}
}
// Servo test
else if (_worker_servo_test) {
_worker_servo_test->update();
if (_worker_servo_test->isDone()) {
_worker_servo_test.reset();
mclog::tagInfo(_tag, "start wifi setup");
_worker_wifi = std::make_unique<WifiSetupWorker>();
}
@@ -76,8 +85,8 @@ void StartupWorker::update()
_worker_wifi->update();
if (_worker_wifi->isDone()) {
_worker_wifi.reset();
mclog::tagInfo(_tag, "startup done");
_is_done = true;
mclog::tagInfo(_tag, "startup back");
_page_startup = std::make_unique<PageStartup>();
}
}
}
@@ -33,6 +33,74 @@ static const std::vector<TimezoneOption_t> _timezone_list = {
{"Tokyo (UTC+9)", "JST-9"}, {"Sydney (UTC+10)", "AEST-10"}, {"Noumea (UTC+11)", "SBT-11"},
{"Auckland (UTC+12)", "NZST-12"}, {"Fiji (UTC+13)", "FJT-13"}, {"Line Islands (UTC+14)", "LINT-14"}};
VolumeSetupWorker::VolumeSetupWorker()
{
mclog::info("VolumeSetupWorker start");
for (int volume = 0; volume <= 100; volume += 5) {
_volume_levels.push_back(volume);
}
uint8_t current_volume = GetHAL().getSpeakerVolume();
int current_index = _volume_levels.size() - 1;
for (size_t i = 0; i < _volume_levels.size(); i++) {
if (_volume_levels[i] >= current_volume) {
current_index = static_cast<int>(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_volume = std::make_unique<Label>(*_panel);
_label_volume->setText(fmt::format("{}%", _volume_levels[current_index]));
_label_volume->setTextFont(&lv_font_montserrat_24);
_label_volume->setTextColor(lv_color_hex(0x26206A));
_label_volume->align(LV_ALIGN_CENTER, 0, -70);
_slider = std::make_unique<Slider>(*_panel);
_slider->align(LV_ALIGN_CENTER, 0, -12);
_slider->setRange(0, _volume_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_volume->setText(fmt::format("{}%", _volume_levels[value]));
_target_volume = _volume_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; });
}
VolumeSetupWorker::~VolumeSetupWorker()
{
auto volume = _volume_levels[_slider->getValue()];
mclog::tagInfo(_tag, "final volume: {}", volume);
GetHAL().setSpeakerVolume(volume, true);
}
void VolumeSetupWorker::update()
{
if (_target_volume != -1) {
GetHAL().setSpeakerVolume(_target_volume, false);
_target_volume = -1;
}
}
TimezoneWorker::TimezoneWorker()
{
_panel = std::make_unique<uitk::lvgl_cpp::Container>(lv_screen_active());

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