Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)

set(PROJECT_VER "1.3.0")
set(PROJECT_VER "1.3.1")
add_definitions(-DFIRMWARE_VERSION=\"${PROJECT_VER}\")

# Add this line to disable the specific warning
Expand Down
21 changes: 21 additions & 0 deletions firmware/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 M5Stack Technology CO LTD

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
8 changes: 7 additions & 1 deletion firmware/main/apps/app_setup/app_setup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,13 @@ void AppSetup::onOpen()
},
{
"AI.Agent",
{{"Power Saving",
{{"General",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
_worker = std::make_unique<XiaozhiGeneralWorker>();
}},
{"Power Saving",
[&]() {
_destroy_menu = true;
_need_warm_reset = true;
Expand Down
2 changes: 2 additions & 0 deletions firmware/main/apps/app_setup/workers/account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ AccountWorker::PanelInfo::PanelInfo(lv_obj_t* parent, int posY, std::string_view
_label_info->setTextColor(lv_color_hex(0x07162C));
_label_info->align(LV_ALIGN_CENTER, 0, 14);
_label_info->setWidth(270);
_label_info->setHeight(30);
_label_info->setTextAlign(LV_TEXT_ALIGN_CENTER);
_label_info->setLongMode(LV_LABEL_LONG_MODE_SCROLL_CIRCULAR);
_label_info->setText(info);
}

Expand Down
99 changes: 99 additions & 0 deletions firmware/main/apps/app_setup/workers/ai_agent.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,19 @@
#include "workers.h"
#include <mooncake_log.h>
#include <hal/hal.h>
#include <array>
#include <vector>

using namespace smooth_ui_toolkit::lvgl_cpp;
using namespace setup_workers;

static std::string _tag = "Setup-AIAgent";

namespace {
static const std::array<const char*, 4> _idle_motion_level_labels = {{"Off", "Low", "Medium", "High"}};

} // namespace

XiaozhiPowerSavingWorker::XiaozhiPowerSavingWorker()
{
mclog::info("XiaozhiPowerSavingWorker start");
Expand Down Expand Up @@ -139,3 +145,96 @@ void XiaozhiPowerSavingWorker::update_idle_label()
auto total_minutes = _config.idleShutdownTimeSeconds / 60;
_label_idle_value->setText(fmt::format("{} min", total_minutes));
}

XiaozhiGeneralWorker::XiaozhiGeneralWorker()
{
mclog::info("XiaozhiGeneralWorker start");

_config = GetHAL().getXiaozhiConfig();

for (uint8_t level = 0; level < _idle_motion_level_labels.size(); ++level) {
_idle_motion_levels.push_back(level);
}

int current_index = static_cast<int>(_idle_motion_levels.size()) - 1;
for (size_t i = 0; i < _idle_motion_levels.size(); ++i) {
if (_idle_motion_levels[i] >= _config.idleRandomMovementLevel) {
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->setPadding(0, 50, 24, 18);
_panel->setScrollDir(LV_DIR_VER);
_panel->setScrollbarMode(LV_SCROLLBAR_MODE_ACTIVE);

_panel_general = std::make_unique<Container>(_panel->get());
_panel_general->setSize(296, 156);
_panel_general->align(LV_ALIGN_TOP_MID, 0, 20);
_panel_general->setBgColor(lv_color_hex(0xD2E3FF));
_panel_general->setBorderWidth(0);
_panel_general->setRadius(18);
_panel_general->setPadding(0, 0, 0, 0);
_panel_general->removeFlag(LV_OBJ_FLAG_SCROLLABLE);

_label_idle_motion_title = std::make_unique<Label>(_panel_general->get());
_label_idle_motion_title->setText("Idle movement frequency:");
_label_idle_motion_title->setTextFont(&lv_font_montserrat_16);
_label_idle_motion_title->setTextColor(lv_color_hex(0x26206A));
_label_idle_motion_title->setWidth(260);
_label_idle_motion_title->setTextAlign(LV_TEXT_ALIGN_CENTER);
_label_idle_motion_title->align(LV_ALIGN_TOP_MID, 0, 18);

_label_idle_motion_value = std::make_unique<Label>(_panel_general->get());
_label_idle_motion_value->setTextFont(&lv_font_montserrat_24);
_label_idle_motion_value->setTextColor(lv_color_hex(0x26206A));
_label_idle_motion_value->align(LV_ALIGN_TOP_MID, 0, 64);

_slider_idle_motion = std::make_unique<Slider>(_panel_general->get());
_slider_idle_motion->align(LV_ALIGN_TOP_MID, 0, 118);
_slider_idle_motion->setRange(0, _idle_motion_levels.size() - 1);
_slider_idle_motion->setSize(250, 18);
_slider_idle_motion->setBgColor(lv_color_hex(0x615B9E), LV_PART_KNOB);
_slider_idle_motion->setBgColor(lv_color_hex(0x615B9E), LV_PART_INDICATOR);
_slider_idle_motion->setBgColor(lv_color_hex(0xB8D3FD), LV_PART_MAIN);
_slider_idle_motion->setBgOpa(255);
_slider_idle_motion->setValue(current_index);
_slider_idle_motion->onValueChanged().connect([this](int32_t value) { _pending_idle_motion_index = value; });

_btn_confirm = std::make_unique<Button>(_panel->get());
apply_button_common_style(*_btn_confirm);
_btn_confirm->align(LV_ALIGN_TOP_MID, 0, 196);
_btn_confirm->setSize(290, 50);
_btn_confirm->label().setText("Confirm");
_btn_confirm->onClick().connect([this]() { _confirm_flag = true; });

update_idle_motion_label();
}

void XiaozhiGeneralWorker::update()
{
if (_pending_idle_motion_index != -1) {
_config.idleRandomMovementLevel = _idle_motion_levels[_pending_idle_motion_index];
_pending_idle_motion_index = -1;
update_idle_motion_label();
}

if (_confirm_flag) {
_confirm_flag = false;
GetHAL().setXiaozhiConfig(_config);
mclog::tagInfo(_tag, "xiaozhi config updated: idleRandomMovementLevel={} ({})", _config.idleRandomMovementLevel,
_idle_motion_level_labels[_config.idleRandomMovementLevel]);
_is_done = true;
}
}

void XiaozhiGeneralWorker::update_idle_motion_label()
{
_label_idle_motion_value->setText(_idle_motion_level_labels[_config.idleRandomMovementLevel]);
}
18 changes: 14 additions & 4 deletions firmware/main/apps/app_setup/workers/display.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ BrightnessSetupWorker::BrightnessSetupWorker()
mclog::info("BrightnessSetupWorker start");

uint8_t current_brightness = GetHAL().getBackLightBrightness();
_original_brightness = current_brightness;
int current_index = 0;
for (size_t i = 0; i < _brightness_levels.size(); i++) {
if (_brightness_levels[i] >= current_brightness) {
Expand Down Expand Up @@ -61,7 +62,10 @@ BrightnessSetupWorker::BrightnessSetupWorker()
_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; });
_btn_confirm->onClick().connect([this]() {
_confirmed = true;
_is_done = true;
});
}

void BrightnessSetupWorker::update()
Expand All @@ -74,7 +78,13 @@ void BrightnessSetupWorker::update()

BrightnessSetupWorker::~BrightnessSetupWorker()
{
auto brightness = _brightness_levels[_slider->getValue()];
mclog::tagInfo(_tag, "final brightness: {}", brightness);
GetHAL().setBackLightBrightness(brightness, true);
if (_confirmed) {
auto brightness = _brightness_levels[_slider->getValue()];
mclog::tagInfo(_tag, "final brightness: {}", brightness);
GetHAL().setBackLightBrightness(brightness, true);
return;
}

mclog::tagInfo(_tag, "brightness change cancelled, restore: {}", _original_brightness);
GetHAL().setBackLightBrightness(_original_brightness, false);
}
18 changes: 14 additions & 4 deletions firmware/main/apps/app_setup/workers/system.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ VolumeSetupWorker::VolumeSetupWorker()
}

uint8_t current_volume = GetHAL().getSpeakerVolume();
_original_volume = current_volume;
int current_index = _volume_levels.size() - 1;
for (size_t i = 0; i < _volume_levels.size(); i++) {
if (_volume_levels[i] >= current_volume) {
Expand Down Expand Up @@ -83,14 +84,23 @@ VolumeSetupWorker::VolumeSetupWorker()
_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; });
_btn_confirm->onClick().connect([this]() {
_confirmed = true;
_is_done = true;
});
}

VolumeSetupWorker::~VolumeSetupWorker()
{
auto volume = _volume_levels[_slider->getValue()];
mclog::tagInfo(_tag, "final volume: {}", volume);
GetHAL().setSpeakerVolume(volume, true);
if (_confirmed) {
auto volume = _volume_levels[_slider->getValue()];
mclog::tagInfo(_tag, "final volume: {}", volume);
GetHAL().setSpeakerVolume(volume, true);
return;
}

mclog::tagInfo(_tag, "volume change cancelled, restore: {}", _original_volume);
GetHAL().setSpeakerVolume(_original_volume, false);
}

void VolumeSetupWorker::update()
Expand Down
33 changes: 31 additions & 2 deletions firmware/main/apps/app_setup/workers/workers.h
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,9 @@ class BrightnessSetupWorker : public WorkerBase {
std::unique_ptr<uitk::lvgl_cpp::Label> _label_brightness;
std::unique_ptr<uitk::lvgl_cpp::Slider> _slider;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_confirm;
int32_t _target_brightness = -1;
uint8_t _original_brightness = 0;
int32_t _target_brightness = -1;
bool _confirmed = false;
};

/**
Expand All @@ -260,7 +262,9 @@ class VolumeSetupWorker : public WorkerBase {
std::unique_ptr<uitk::lvgl_cpp::Slider> _slider;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_confirm;
std::vector<uint8_t> _volume_levels;
int32_t _target_volume = -1;
uint8_t _original_volume = 0;
int32_t _target_volume = -1;
bool _confirmed = false;
};

/**
Expand Down Expand Up @@ -291,6 +295,31 @@ class XiaozhiPowerSavingWorker : public WorkerBase {
bool _confirm_flag = false;
};

/**
* @brief
*
*/
class XiaozhiGeneralWorker : public WorkerBase {
public:
XiaozhiGeneralWorker();
void update() override;

private:
void update_idle_motion_label();

std::unique_ptr<uitk::lvgl_cpp::Container> _panel;
std::unique_ptr<uitk::lvgl_cpp::Container> _panel_general;
std::unique_ptr<uitk::lvgl_cpp::Label> _label_idle_motion_title;
std::unique_ptr<uitk::lvgl_cpp::Label> _label_idle_motion_value;
std::unique_ptr<uitk::lvgl_cpp::Slider> _slider_idle_motion;
std::unique_ptr<uitk::lvgl_cpp::Button> _btn_confirm;

XiaozhiConfig_t _config;
std::vector<uint8_t> _idle_motion_levels;
int32_t _pending_idle_motion_index = -1;
bool _confirm_flag = false;
};

/**
* @brief
*
Expand Down
8 changes: 6 additions & 2 deletions firmware/main/hal/board/hal_bridge.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
static const char* _tag = "HAL_BRIDGE";

static constexpr std::string_view _xiaozhi_config_nvs_ns = "xiaozhi";
static constexpr std::string_view _xiaozhi_config_idle_shutdown_time_key = "idle_shutdown";
static constexpr std::string_view _xiaozhi_config_allow_shutdown_when_charging_key = "shutdown_charge";
static constexpr std::string_view _xiaozhi_config_idle_shutdown_time_key = "idle_sec";
static constexpr std::string_view _xiaozhi_config_allow_shutdown_when_charging_key = "ext_pwr";
static constexpr std::string_view _xiaozhi_config_idle_random_movement_key = "idle_lv";

namespace hal_bridge {

Expand Down Expand Up @@ -126,6 +127,8 @@ XiaozhiConfig_t get_xiaozhi_config()
static_cast<int>(config.idleShutdownTimeSeconds));
config.allowShutdownWhenCharging =
settings.GetBool(_xiaozhi_config_allow_shutdown_when_charging_key.data(), config.allowShutdownWhenCharging);
config.idleRandomMovementLevel =
settings.GetInt(_xiaozhi_config_idle_random_movement_key.data(), config.idleRandomMovementLevel);

return config;
}
Expand All @@ -135,6 +138,7 @@ void set_xiaozhi_config(const XiaozhiConfig_t& config)
Settings settings(_xiaozhi_config_nvs_ns.data(), true);
settings.SetInt(_xiaozhi_config_idle_shutdown_time_key.data(), config.idleShutdownTimeSeconds);
settings.SetBool(_xiaozhi_config_allow_shutdown_when_charging_key.data(), config.allowShutdownWhenCharging);
settings.SetInt(_xiaozhi_config_idle_random_movement_key.data(), config.idleRandomMovementLevel);
}

void app_play_sound(const std::string_view& sound)
Expand Down
1 change: 1 addition & 0 deletions firmware/main/hal/board/hal_bridge.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ struct Data_t {
struct XiaozhiConfig_t {
uint32_t idleShutdownTimeSeconds = 600;
bool allowShutdownWhenCharging = false;
uint8_t idleRandomMovementLevel = 2;
};

void lock();
Expand Down
28 changes: 27 additions & 1 deletion firmware/main/hal/board/stackchan_display.cc
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,9 @@ void StackChanAvatarDisplay::SetupUI()

// GetHAL().startStackChanAutoUpdate(24);

auto config = hal_bridge::get_xiaozhi_config();
idle_motion_level_ = config.idleRandomMovementLevel;

ESP_LOGI(TAG, "Avatar created and started");
}

Expand All @@ -287,6 +290,27 @@ void StackChanAvatarDisplay::LvglUnlock()
Unlock();
}

void StackChanAvatarDisplay::CreateIdleMotionModifier()
{
auto& stackchan = GetStackChan();

switch (idle_motion_level_) {
case 0:
idle_motion_modifier_id_ = -1;
return;
case 1:
idle_motion_modifier_id_ = stackchan.addModifier(std::make_unique<IdleMotionModifier>(8000, 12000));
return;
case 3:
idle_motion_modifier_id_ = stackchan.addModifier(std::make_unique<IdleMotionModifier>(2000, 4000));
return;
case 2:
default:
idle_motion_modifier_id_ = stackchan.addModifier(std::make_unique<IdleMotionModifier>());
return;
}
}

void StackChanAvatarDisplay::SetEmotion(const char* emotion)
{
auto& stackchan = GetStackChan();
Expand Down Expand Up @@ -505,7 +529,9 @@ void StackChanAvatarDisplay::SetStatus(const char* status)
// Start idle motion
ESP_LOGW(TAG, "Start idle motion");
if (idle_motion_modifier_id_ < 0) {
idle_motion_modifier_id_ = stackchan.addModifier(std::make_unique<IdleMotionModifier>());
if (idle_motion_level_ > 0) {
CreateIdleMotionModifier();
}
idle_expression_modifier_id_ = stackchan.addModifier(std::make_unique<IdleExpressionModifier>());
}

Expand Down
Loading