add firmware source code (#4)

* add firmware source code
This commit is contained in:
Forairaaaaa
2026-01-08 09:18:20 +08:00
committed by GitHub
parent 4f1373e449
commit 5001b7081b
164 changed files with 81663 additions and 1 deletions
@@ -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