From 3f9762261d9f48d17746bb83d7e8d7303487385b Mon Sep 17 00:00:00 2001 From: Daniel Patriche Date: Sun, 10 May 2026 20:04:57 +0300 Subject: [PATCH] feat(firmware): add configurable idle head motion Adds an on-device "Idle Behavior" screen under Setup -> AI.Agent that exposes three controls for the randomized head motion in AI Agent STANDBY: - Enable toggle - Frequency preset (Sparse / Normal / Frequent), maps to interval ranges - Intensity preset (Calm / Normal / Lively), scales motion travel Defaults preserve current behavior. When disabled, IdleMotionModifier is not added, so head petting and other motions stay fully responsive. Persisted in NVS via the existing XiaozhiConfig_t / Settings wrapper. UI mirrors XiaozhiPowerSavingWorker. Side change in StackChanAvatarDisplay::SetStatus and the sleepy emotion handler: IdleMotionModifier and IdleExpressionModifier were previously added and removed together under a single id check. Decoupled into independent checks so the motion modifier can be omitted while keeping the expression idling. --- firmware/main/apps/app_setup/app_setup.cpp | 6 + .../main/apps/app_setup/workers/ai_agent.cpp | 175 ++++++++++++++++++ .../main/apps/app_setup/workers/workers.h | 37 ++++ firmware/main/hal/board/hal_bridge.cc | 12 ++ firmware/main/hal/board/hal_bridge.h | 3 + firmware/main/hal/board/stackchan_display.cc | 14 +- firmware/main/hal/hal.cpp | 6 + firmware/main/hal/hal.h | 3 + .../main/stackchan/modifiers/idle_motion.h | 44 ++++- 9 files changed, 289 insertions(+), 11 deletions(-) diff --git a/firmware/main/apps/app_setup/app_setup.cpp b/firmware/main/apps/app_setup/app_setup.cpp index 2b4d98d..ba1759b 100644 --- a/firmware/main/apps/app_setup/app_setup.cpp +++ b/firmware/main/apps/app_setup/app_setup.cpp @@ -77,6 +77,12 @@ void AppSetup::onOpen() _destroy_menu = true; _need_warm_reset = true; _worker = std::make_unique(); + }}, + {"Idle Behavior", + [&]() { + _destroy_menu = true; + _need_warm_reset = true; + _worker = std::make_unique(); }}}, }, { diff --git a/firmware/main/apps/app_setup/workers/ai_agent.cpp b/firmware/main/apps/app_setup/workers/ai_agent.cpp index abd4771..37ba70a 100644 --- a/firmware/main/apps/app_setup/workers/ai_agent.cpp +++ b/firmware/main/apps/app_setup/workers/ai_agent.cpp @@ -139,3 +139,178 @@ void XiaozhiPowerSavingWorker::update_idle_label() auto total_minutes = _config.idleShutdownTimeSeconds / 60; _label_idle_value->setText(fmt::format("{} min", total_minutes)); } + +static const char* frequency_label(uint8_t preset) +{ + switch (preset) { + case 0: return "Sparse"; + case 2: return "Frequent"; + default: return "Normal"; + } +} + +static const char* intensity_label(uint8_t preset) +{ + switch (preset) { + case 0: return "Calm"; + case 2: return "Lively"; + default: return "Normal"; + } +} + +IdleBehaviorWorker::IdleBehaviorWorker() +{ + mclog::info("IdleBehaviorWorker start"); + + _config = GetHAL().getXiaozhiConfig(); + + _panel = std::make_unique(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_enable = std::make_unique(_panel->get()); + _panel_enable->setSize(296, 120); + _panel_enable->align(LV_ALIGN_TOP_MID, 0, 20); + _panel_enable->setBgColor(lv_color_hex(0xD2E3FF)); + _panel_enable->setBorderWidth(0); + _panel_enable->setRadius(18); + _panel_enable->setPadding(0, 0, 0, 0); + _panel_enable->removeFlag(LV_OBJ_FLAG_SCROLLABLE); + + _label_enable_title = std::make_unique