mirror of
https://github.com/m5stack/StackChan.git
synced 2026-05-20 11:49:45 -07:00
Merge pull request #58 from m5stack/firmware-dev
update firmware v1.3.1
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#include "workers.h"
|
||||
#include <mooncake_log.h>
|
||||
#include <hal/hal.h>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
|
||||
using namespace smooth_ui_toolkit::lvgl_cpp;
|
||||
@@ -13,6 +14,11 @@ 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");
|
||||
@@ -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]);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -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()
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
@@ -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()
|
||||
|
||||
@@ -241,7 +241,9 @@ private:
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -260,7 +262,9 @@ private:
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -291,6 +295,31 @@ private:
|
||||
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
|
||||
*
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -27,6 +27,7 @@ struct Data_t {
|
||||
struct XiaozhiConfig_t {
|
||||
uint32_t idleShutdownTimeSeconds = 600;
|
||||
bool allowShutdownWhenCharging = false;
|
||||
uint8_t idleRandomMovementLevel = 2;
|
||||
};
|
||||
|
||||
void lock();
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
@@ -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>());
|
||||
}
|
||||
|
||||
|
||||
@@ -19,11 +19,14 @@ private:
|
||||
int idle_expression_modifier_id_ = -1;
|
||||
int blink_modifier_id_ = -1;
|
||||
bool is_sleeping_ = false;
|
||||
uint8_t idle_motion_level_ = 2;
|
||||
|
||||
lv_obj_t* preview_image_ = nullptr;
|
||||
esp_timer_handle_t preview_timer_ = nullptr;
|
||||
std::unique_ptr<LvglImage> preview_image_cached_ = nullptr;
|
||||
|
||||
void CreateIdleMotionModifier();
|
||||
|
||||
protected:
|
||||
virtual bool Lock(int timeout_ms = 0) override;
|
||||
virtual void Unlock() override;
|
||||
|
||||
@@ -208,6 +208,7 @@ XiaozhiConfig_t Hal::getXiaozhiConfig()
|
||||
return XiaozhiConfig_t{
|
||||
.idleShutdownTimeSeconds = bridge_config.idleShutdownTimeSeconds,
|
||||
.allowShutdownWhenCharging = bridge_config.allowShutdownWhenCharging,
|
||||
.idleRandomMovementLevel = bridge_config.idleRandomMovementLevel,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -216,6 +217,7 @@ void Hal::setXiaozhiConfig(XiaozhiConfig_t config)
|
||||
hal_bridge::set_xiaozhi_config({
|
||||
.idleShutdownTimeSeconds = config.idleShutdownTimeSeconds,
|
||||
.allowShutdownWhenCharging = config.allowShutdownWhenCharging,
|
||||
.idleRandomMovementLevel = config.idleRandomMovementLevel,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -117,6 +117,7 @@ struct UserAccountInfo_t {
|
||||
struct XiaozhiConfig_t {
|
||||
uint32_t idleShutdownTimeSeconds = 600;
|
||||
bool allowShutdownWhenCharging = false;
|
||||
uint8_t idleRandomMovementLevel = 2;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -180,7 +180,7 @@ void Hal::servo_init()
|
||||
ServoConfig_t pitch_servo_config;
|
||||
pitch_servo_config.id = 2;
|
||||
pitch_servo_config.defaultZeroPos = 620;
|
||||
pitch_servo_config.angleLimit = Vector2i(0, 900);
|
||||
pitch_servo_config.angleLimit = Vector2i(30, 870);
|
||||
pitch_servo_config.rawPosLimit = Vector2i(0, 1000);
|
||||
pitch_servo_config.settingNs = "servo";
|
||||
pitch_servo_config.settingZeroPositionKey = "zero_pos_2";
|
||||
|
||||
Reference in New Issue
Block a user