mirror of
https://github.com/m5stack/StackChan.git
synced 2026-04-27 19:12:40 +00:00
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "app_setup.h"
|
||||
#include <hal/hal.h>
|
||||
#include <mooncake.h>
|
||||
#include <mooncake_log.h>
|
||||
#include <assets/assets.h>
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <apps/common/common.h>
|
||||
|
||||
using namespace mooncake;
|
||||
using namespace view;
|
||||
using namespace setup_workers;
|
||||
|
||||
AppSetup::AppSetup()
|
||||
{
|
||||
// 配置 App 名
|
||||
setAppInfo().name = "SETUP";
|
||||
// 配置 App 图标
|
||||
setAppInfo().icon = (void*)&icon_setup;
|
||||
// 配置 App 主题颜色
|
||||
static uint32_t theme_color = 0xB3B3B3;
|
||||
setAppInfo().userData = (void*)&theme_color;
|
||||
}
|
||||
|
||||
void AppSetup::onCreate()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on create");
|
||||
// open();
|
||||
}
|
||||
|
||||
void AppSetup::onOpen()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on open");
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_menu_sections = {{
|
||||
"Connectivity",
|
||||
{
|
||||
// {"Set Up Wi-Fi",
|
||||
// [&]() {
|
||||
// _destroy_menu = true;
|
||||
// _worker = std::make_unique<WifiSetupWorker>();
|
||||
// }},
|
||||
{"App Bind Code",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<AppBindCodeWorker>();
|
||||
}}},
|
||||
},
|
||||
{
|
||||
"Servo",
|
||||
{{"Zero Calibration",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<ZeroCalibrationWorker>();
|
||||
}},
|
||||
{"LED Strips Test",
|
||||
[&]() {
|
||||
_destroy_menu = true;
|
||||
_worker = std::make_unique<RgbTestWorker>();
|
||||
}}},
|
||||
},
|
||||
{
|
||||
"About",
|
||||
{{fmt::format("FW Version: {}", common::FirmwareVersion), nullptr}},
|
||||
},
|
||||
{
|
||||
"End",
|
||||
{{"Quit", [&]() { close(); }}},
|
||||
}};
|
||||
|
||||
_menu_page = std::make_unique<view::SelectMenuPage>(_menu_sections);
|
||||
}
|
||||
|
||||
void AppSetup::onRunning()
|
||||
{
|
||||
LvglLockGuard lock;
|
||||
|
||||
if (_menu_page) {
|
||||
_menu_page->update();
|
||||
}
|
||||
|
||||
if (_destroy_menu) {
|
||||
_menu_page.reset();
|
||||
_destroy_menu = false;
|
||||
}
|
||||
|
||||
if (_worker) {
|
||||
_worker->update();
|
||||
if (_worker->isDone()) {
|
||||
_worker.reset();
|
||||
_menu_page = std::make_unique<view::SelectMenuPage>(_menu_sections);
|
||||
}
|
||||
}
|
||||
|
||||
GetStackChan().update();
|
||||
}
|
||||
|
||||
void AppSetup::onClose()
|
||||
{
|
||||
mclog::tagInfo(getAppInfo().name, "on close");
|
||||
|
||||
LvglLockGuard lock;
|
||||
|
||||
_menu_page.reset();
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "view/view.h"
|
||||
#include "workers/workers.h"
|
||||
#include <mooncake.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
/**
|
||||
* @brief 派生 App
|
||||
*
|
||||
*/
|
||||
class AppSetup : public mooncake::AppAbility {
|
||||
public:
|
||||
AppSetup();
|
||||
|
||||
// 重写生命周期回调
|
||||
void onCreate() override;
|
||||
void onOpen() override;
|
||||
void onRunning() override;
|
||||
void onClose() override;
|
||||
|
||||
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;
|
||||
};
|
||||
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "view.h"
|
||||
|
||||
using namespace view;
|
||||
using namespace uitk::lvgl_cpp;
|
||||
|
||||
SelectMenuPage::SelectMenuPage(std::vector<MenuSection> sections) : _sections(std::move(sections))
|
||||
{
|
||||
_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->setBorderWidth(0);
|
||||
_pannel->setRadius(0);
|
||||
_pannel->setScrollDir(LV_DIR_VER);
|
||||
|
||||
int cursor_y = 10;
|
||||
|
||||
for (int i = 0; i < _sections.size(); ++i) {
|
||||
const auto& section = _sections[i];
|
||||
create_selection_label(20, cursor_y, section.title);
|
||||
cursor_y += 24 + 12;
|
||||
|
||||
for (int j = 0; j < section.items.size(); ++j) {
|
||||
const auto& item = section.items[j];
|
||||
create_item_button(cursor_y, item, i, j);
|
||||
cursor_y += 48 + 12;
|
||||
}
|
||||
cursor_y += 8;
|
||||
}
|
||||
cursor_y += 20;
|
||||
}
|
||||
|
||||
void SelectMenuPage::update()
|
||||
{
|
||||
if (_pending_section_index >= 0 && _pending_item_index >= 0) {
|
||||
if (_pending_section_index < _sections.size()) {
|
||||
auto& section = _sections[_pending_section_index];
|
||||
if (_pending_item_index < section.items.size()) {
|
||||
auto& item = section.items[_pending_item_index];
|
||||
if (item.onClick) {
|
||||
item.onClick();
|
||||
}
|
||||
}
|
||||
}
|
||||
_pending_section_index = -1;
|
||||
_pending_item_index = -1;
|
||||
}
|
||||
}
|
||||
|
||||
void SelectMenuPage::create_selection_label(int x, int y, std::string_view text)
|
||||
{
|
||||
auto label = std::make_unique<uitk::lvgl_cpp::Label>(*_pannel);
|
||||
label->setText(text);
|
||||
label->setTextFont(&lv_font_montserrat_16);
|
||||
label->setTextColor(lv_color_hex(0x6A6882));
|
||||
label->setPos(x, y);
|
||||
_labels.push_back(std::move(label));
|
||||
}
|
||||
|
||||
void SelectMenuPage::create_item_button(int y, const MenuItem& item, int section_idx, int item_idx)
|
||||
{
|
||||
auto btn = std::make_unique<uitk::lvgl_cpp::Button>(*_pannel);
|
||||
btn->setSize(282, 48);
|
||||
btn->align(LV_ALIGN_TOP_MID, 0, y);
|
||||
btn->setBgColor(lv_color_hex(0xB8D3FD));
|
||||
btn->setBorderWidth(0);
|
||||
btn->setShadowWidth(0);
|
||||
btn->setRadius(12);
|
||||
|
||||
btn->label().setText(item.label);
|
||||
btn->label().setTextFont(&lv_font_montserrat_24);
|
||||
btn->label().setTextColor(lv_color_hex(0x26206A));
|
||||
btn->label().align(LV_ALIGN_CENTER, 0, 0);
|
||||
btn->label().setWidth(256);
|
||||
btn->label().setTextAlign(LV_TEXT_ALIGN_CENTER);
|
||||
btn->label().setLongMode(LV_LABEL_LONG_MODE_SCROLL_CIRCULAR);
|
||||
|
||||
if (item.onClick) {
|
||||
btn->onClick().connect([this, section_idx, item_idx]() {
|
||||
_pending_section_index = section_idx;
|
||||
_pending_item_index = item_idx;
|
||||
});
|
||||
}
|
||||
_buttons.push_back(std::move(btn));
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include <string_view>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
namespace view {
|
||||
|
||||
class SelectMenuPage {
|
||||
public:
|
||||
struct MenuItem {
|
||||
std::string label;
|
||||
std::function<void()> onClick;
|
||||
};
|
||||
|
||||
struct MenuSection {
|
||||
std::string title;
|
||||
std::vector<MenuItem> items;
|
||||
};
|
||||
|
||||
SelectMenuPage(std::vector<MenuSection> sections);
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
std::vector<MenuSection> _sections;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _pannel;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Label>> _labels;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Button>> _buttons;
|
||||
int _pending_section_index = -1;
|
||||
int _pending_item_index = -1;
|
||||
|
||||
void create_selection_label(int x, int y, std::string_view text);
|
||||
void create_item_button(int y, const MenuItem& item, int section_idx, int item_idx);
|
||||
};
|
||||
|
||||
} // namespace view
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
|
||||
namespace setup_workers {
|
||||
|
||||
inline void apply_button_common_style(uitk::lvgl_cpp::Button& btn)
|
||||
{
|
||||
btn.setBgColor(lv_color_hex(0xB8D3FD));
|
||||
btn.setBorderWidth(0);
|
||||
btn.setShadowWidth(0);
|
||||
btn.setRadius(18);
|
||||
btn.label().setTextFont(&lv_font_montserrat_24);
|
||||
btn.label().setTextColor(lv_color_hex(0x26206A));
|
||||
}
|
||||
|
||||
} // namespace setup_workers
|
||||
@@ -0,0 +1,273 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "workers.h"
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <ArduinoJson.hpp>
|
||||
#include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
#include <memory>
|
||||
|
||||
using namespace smooth_ui_toolkit::lvgl_cpp;
|
||||
using namespace setup_workers;
|
||||
using namespace stackchan;
|
||||
|
||||
static std::string _tag = "Setup-Connectivity";
|
||||
|
||||
WifiSetupWorker::WifiSetupWorker()
|
||||
{
|
||||
_state = State::AppDownload;
|
||||
_last_state = State::None;
|
||||
_is_first_in = true;
|
||||
|
||||
// Create default avatar
|
||||
auto avatar = std::make_unique<avatar::DefaultAvatar>();
|
||||
avatar->init(lv_screen_active(), &lv_font_montserrat_24);
|
||||
GetStackChan().attachAvatar(std::move(avatar));
|
||||
|
||||
_app_config_signal_id =
|
||||
GetHAL().onAppConfigEvent.connect([this](AppConfigEvent event) { _last_app_config_event = event; });
|
||||
|
||||
GetHAL().startAppConfigServer();
|
||||
}
|
||||
|
||||
WifiSetupWorker::~WifiSetupWorker()
|
||||
{
|
||||
GetHAL().onAppConfigEvent.disconnect(_app_config_signal_id);
|
||||
GetStackChan().resetAvatar();
|
||||
}
|
||||
|
||||
void WifiSetupWorker::update()
|
||||
{
|
||||
cleanup_ui();
|
||||
update_state();
|
||||
}
|
||||
|
||||
void WifiSetupWorker::update_state()
|
||||
{
|
||||
switch (_state) {
|
||||
case State::AppDownload: {
|
||||
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.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->update(qrcode_text);
|
||||
data.qrcode->align(LV_ALIGN_CENTER, -60, -25);
|
||||
|
||||
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; });
|
||||
}
|
||||
|
||||
if (_state_app_download_data.is_clicked) {
|
||||
switch_state(State::WaitAppConnection);
|
||||
}
|
||||
|
||||
// Check events
|
||||
if (_last_app_config_event != AppConfigEvent::None) {
|
||||
if (_last_app_config_event == AppConfigEvent::AppConnected) {
|
||||
switch_state(State::AppConnected);
|
||||
}
|
||||
_last_app_config_event = AppConfigEvent::None;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State::WaitAppConnection: {
|
||||
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;
|
||||
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([]() {
|
||||
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()));
|
||||
}
|
||||
|
||||
// Check events
|
||||
if (_last_app_config_event != AppConfigEvent::None) {
|
||||
if (_last_app_config_event == AppConfigEvent::AppConnected) {
|
||||
switch_state(State::AppConnected);
|
||||
}
|
||||
_last_app_config_event = AppConfigEvent::None;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State::AppConnected: {
|
||||
if (_is_first_in) {
|
||||
_is_first_in = false;
|
||||
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.leftEye().setVisible(true);
|
||||
avatar.rightEye().setVisible(true);
|
||||
avatar.mouth().setVisible(true);
|
||||
avatar.setSpeech("Ready to Configure ~");
|
||||
|
||||
GetStackChan().addModifier(std::make_unique<TimedEmotionModifier>(avatar::Emotion::Happy, 4000));
|
||||
GetStackChan().addModifier(std::make_unique<BreathModifier>());
|
||||
GetStackChan().addModifier(std::make_unique<BlinkModifier>());
|
||||
GetStackChan().addModifier(std::make_unique<SpeakingModifier>(2000, 180, false));
|
||||
}
|
||||
|
||||
// Check events
|
||||
if (_last_app_config_event != AppConfigEvent::None) {
|
||||
if (_last_app_config_event == AppConfigEvent::AppDisconnected) {
|
||||
switch_state(State::WaitAppConnection);
|
||||
} else if (_last_app_config_event == AppConfigEvent::TryWifiConnect) {
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.setSpeech("Verifying...");
|
||||
GetStackChan().addModifier(std::make_unique<SpeakingModifier>(2000, 180, false));
|
||||
} else if (_last_app_config_event == AppConfigEvent::WifiConnectFailed) {
|
||||
GetStackChan().addModifier(std::make_unique<TimedEmotionModifier>(avatar::Emotion::Sad, 4000));
|
||||
GetStackChan().addModifier(
|
||||
std::make_unique<TimedSpeechModifier>("Connect Failed. Try again?", 6000));
|
||||
GetStackChan().addModifier(std::make_unique<SpeakingModifier>(3000, 180, false));
|
||||
} else if (_last_app_config_event == AppConfigEvent::WifiConnected) {
|
||||
switch_state(State::Done);
|
||||
}
|
||||
_last_app_config_event = AppConfigEvent::None;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case State::Done: {
|
||||
if (_is_first_in) {
|
||||
_is_first_in = false;
|
||||
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.leftEye().setVisible(true);
|
||||
avatar.rightEye().setVisible(true);
|
||||
avatar.mouth().setVisible(true);
|
||||
avatar.setEmotion(avatar::Emotion::Happy);
|
||||
|
||||
GetStackChan().addModifier(std::make_unique<SpeakingModifier>(1500, 180, false));
|
||||
|
||||
_state_done_data.reboot_count = 4;
|
||||
}
|
||||
|
||||
if (GetHAL().millis() - _last_tick > 1000) {
|
||||
_last_tick = GetHAL().millis();
|
||||
if (_state_done_data.reboot_count > 0) {
|
||||
_state_done_data.reboot_count--;
|
||||
auto& avatar = GetStackChan().avatar();
|
||||
avatar.setSpeech(fmt::format("Done! Reboot in {}s.", _state_done_data.reboot_count));
|
||||
} else {
|
||||
mclog::tagInfo(_tag, "rebooting...");
|
||||
GetHAL().delay(100);
|
||||
GetHAL().reboot();
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WifiSetupWorker::cleanup_ui()
|
||||
{
|
||||
if (_last_state == State::None) {
|
||||
return;
|
||||
}
|
||||
|
||||
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: {
|
||||
GetStackChan().avatar().setSpeech("");
|
||||
GetStackChan().clearModifiers();
|
||||
break;
|
||||
}
|
||||
case State::Done: {
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
_last_state = State::None;
|
||||
}
|
||||
|
||||
void WifiSetupWorker::switch_state(State newState)
|
||||
{
|
||||
_last_state = _state;
|
||||
_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,127 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#include "workers.h"
|
||||
#include <stackchan/stackchan.h>
|
||||
#include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
|
||||
using namespace smooth_ui_toolkit::lvgl_cpp;
|
||||
using namespace setup_workers;
|
||||
|
||||
static std::string _tag = "Setup-Servo";
|
||||
|
||||
ZeroCalibrationWorker::ZeroCalibrationWorker()
|
||||
{
|
||||
_pannel = std::make_unique<Container>(lv_screen_active());
|
||||
_pannel->setBgColor(lv_color_hex(0xFFFFFF));
|
||||
_pannel->removeFlag(LV_OBJ_FLAG_SCROLLABLE);
|
||||
_pannel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
_pannel->setBorderWidth(0);
|
||||
_pannel->setSize(320, 240);
|
||||
_pannel->setRadius(0);
|
||||
|
||||
_btn_go_home = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*_btn_go_home);
|
||||
_btn_go_home->setAlign(LV_ALIGN_CENTER);
|
||||
_btn_go_home->setPos(0, -85);
|
||||
_btn_go_home->setSize(230, 55);
|
||||
_btn_go_home->label().setText("Move To Home");
|
||||
_btn_go_home->onClick().connect([this]() { _go_home_flag = true; });
|
||||
|
||||
_btn_confirm = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*_btn_confirm);
|
||||
_btn_confirm->setAlign(LV_ALIGN_CENTER);
|
||||
_btn_confirm->setPos(0, 0);
|
||||
_btn_confirm->setSize(290, 70);
|
||||
_btn_confirm->label().setText("Set Current Position\nAs Home");
|
||||
_btn_confirm->label().setTextAlign(LV_TEXT_ALIGN_CENTER);
|
||||
_btn_confirm->onClick().connect([this]() { _confirm_flag = true; });
|
||||
|
||||
_btn_quit = std::make_unique<Button>(lv_screen_active());
|
||||
apply_button_common_style(*_btn_quit);
|
||||
_btn_quit->setAlign(LV_ALIGN_CENTER);
|
||||
_btn_quit->setPos(0, 85);
|
||||
_btn_quit->setSize(230, 55);
|
||||
_btn_quit->label().setText("Done");
|
||||
_btn_quit->onClick().connect([this]() { _is_done = true; });
|
||||
|
||||
auto& motion = GetStackChan().motion();
|
||||
motion.setAutoAngleSyncEnabled(true);
|
||||
}
|
||||
|
||||
void ZeroCalibrationWorker::update()
|
||||
{
|
||||
if (_confirm_flag) {
|
||||
_confirm_flag = false;
|
||||
|
||||
mclog::tagInfo(_tag, "set current angle as zero");
|
||||
|
||||
auto& motion = GetStackChan().motion();
|
||||
motion.yawServo().setCurrentAngleAsZero();
|
||||
motion.pitchServo().setCurrentAngleAsZero();
|
||||
}
|
||||
|
||||
if (_go_home_flag) {
|
||||
_go_home_flag = false;
|
||||
|
||||
mclog::tagInfo(_tag, "go home");
|
||||
|
||||
auto& motion = GetStackChan().motion();
|
||||
motion.goHome(300);
|
||||
}
|
||||
}
|
||||
|
||||
struct RgbColorEntry {
|
||||
std::string name;
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
static const std::vector<RgbColorEntry> _rgb_colors = {
|
||||
{"Red", 255, 0, 0}, {"Green", 0, 255, 0}, {"Blue", 0, 0, 255}, {"Yellow", 255, 255, 0},
|
||||
{"Cyan", 0, 255, 255}, {"Magenta", 255, 0, 255}, {"White", 255, 255, 255}, {"Off", 0, 0, 0},
|
||||
};
|
||||
|
||||
RgbTestWorker::RgbTestWorker()
|
||||
{
|
||||
_pannel = std::make_unique<Container>(lv_screen_active());
|
||||
_pannel->setBgColor(lv_color_hex(0xFFFFFF));
|
||||
_pannel->align(LV_ALIGN_CENTER, 0, 0);
|
||||
_pannel->setBorderWidth(0);
|
||||
_pannel->setSize(320, 240);
|
||||
_pannel->setRadius(0);
|
||||
_pannel->setFlexFlow(LV_FLEX_FLOW_COLUMN);
|
||||
_pannel->setFlexAlign(LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
|
||||
_pannel->setPadding(20, 20, 20, 20);
|
||||
_pannel->setPadRow(15);
|
||||
|
||||
for (const auto& color : _rgb_colors) {
|
||||
auto btn = std::make_unique<Button>(*_pannel);
|
||||
apply_button_common_style(*btn);
|
||||
btn->setSize(200, 50);
|
||||
btn->label().setText(color.name);
|
||||
|
||||
uint8_t r = color.r;
|
||||
uint8_t g = color.g;
|
||||
uint8_t b = color.b;
|
||||
btn->onClick().connect([r, g, b]() { GetHAL().showRgbColor(r, g, b); });
|
||||
|
||||
_buttons.push_back(std::move(btn));
|
||||
}
|
||||
|
||||
auto btn_quit = std::make_unique<Button>(*_pannel);
|
||||
apply_button_common_style(*btn_quit);
|
||||
btn_quit->setSize(200, 50);
|
||||
btn_quit->label().setText("Quit");
|
||||
btn_quit->onClick().connect([this]() { _is_done = true; });
|
||||
_buttons.push_back(std::move(btn_quit));
|
||||
}
|
||||
|
||||
RgbTestWorker::~RgbTestWorker()
|
||||
{
|
||||
GetHAL().showRgbColor(0, 0, 0);
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*/
|
||||
#pragma once
|
||||
#include "common.h"
|
||||
#include <smooth_lvgl.hpp>
|
||||
#include <uitk/short_namespace.hpp>
|
||||
#include <hal/hal.h>
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace setup_workers {
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class WorkerBase {
|
||||
public:
|
||||
virtual ~WorkerBase() = default;
|
||||
|
||||
virtual void update()
|
||||
{
|
||||
}
|
||||
|
||||
bool isDone() const
|
||||
{
|
||||
return _is_done;
|
||||
}
|
||||
|
||||
protected:
|
||||
bool _is_done = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class ZeroCalibrationWorker : public WorkerBase {
|
||||
public:
|
||||
ZeroCalibrationWorker();
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _pannel;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_quit;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_confirm;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_go_home;
|
||||
bool _confirm_flag = false;
|
||||
bool _go_home_flag = false;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class WifiSetupWorker : public WorkerBase {
|
||||
public:
|
||||
WifiSetupWorker();
|
||||
~WifiSetupWorker();
|
||||
void update() override;
|
||||
|
||||
private:
|
||||
enum class State {
|
||||
None,
|
||||
AppDownload,
|
||||
WaitAppConnection,
|
||||
AppConnected,
|
||||
Done,
|
||||
};
|
||||
|
||||
State _state = State::AppDownload;
|
||||
State _last_state = State::None;
|
||||
|
||||
uint32_t _last_tick = 0;
|
||||
bool _is_first_in = false;
|
||||
|
||||
AppConfigEvent _last_app_config_event = AppConfigEvent::None;
|
||||
int _app_config_signal_id = -1;
|
||||
|
||||
struct StateAppDownloadData {
|
||||
std::unique_ptr<uitk::lvgl_cpp::Qrcode> qrcode;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> btn;
|
||||
bool is_clicked = false;
|
||||
|
||||
void reset()
|
||||
{
|
||||
qrcode.reset();
|
||||
btn.reset();
|
||||
is_clicked = false;
|
||||
}
|
||||
};
|
||||
StateAppDownloadData _state_app_download_data;
|
||||
|
||||
struct StateWaitAppConnectionData {
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> id_btn;
|
||||
|
||||
void reset()
|
||||
{
|
||||
id_btn.reset();
|
||||
}
|
||||
};
|
||||
StateWaitAppConnectionData _state_wait_app_connection_data;
|
||||
|
||||
struct StateDoneData {
|
||||
int reboot_count = 0;
|
||||
};
|
||||
StateDoneData _state_done_data;
|
||||
|
||||
void update_state();
|
||||
void cleanup_ui();
|
||||
void switch_state(State newState);
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief
|
||||
*
|
||||
*/
|
||||
class AppBindCodeWorker : public WorkerBase {
|
||||
public:
|
||||
AppBindCodeWorker();
|
||||
~AppBindCodeWorker();
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Qrcode> _qrcode;
|
||||
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_quit;
|
||||
};
|
||||
|
||||
class RgbTestWorker : public WorkerBase {
|
||||
public:
|
||||
RgbTestWorker();
|
||||
~RgbTestWorker();
|
||||
|
||||
private:
|
||||
std::unique_ptr<uitk::lvgl_cpp::Container> _pannel;
|
||||
std::vector<std::unique_ptr<uitk::lvgl_cpp::Button>> _buttons;
|
||||
};
|
||||
|
||||
} // namespace setup_workers
|
||||
Reference in New Issue
Block a user