diff --git a/firmware/main/apps/app_avatar/app_avatar.cpp b/firmware/main/apps/app_avatar/app_avatar.cpp index 8d2eaf7b..b0d16fbc 100644 --- a/firmware/main/apps/app_avatar/app_avatar.cpp +++ b/firmware/main/apps/app_avatar/app_avatar.cpp @@ -247,6 +247,24 @@ void AppAvatar::onOpen() } }); + GetHAL().onWsDeviceControl.connect([&](const WsDeviceControl_t& control) { + LvglLockGuard lvgl_lock; + + switch (control.action) { + case WsDeviceControlAction::Sleep: + GetHAL().enterRemoteSleepMode(); + break; + case WsDeviceControlAction::Wake: + GetHAL().exitRemoteSleepMode(); + break; + case WsDeviceControlAction::PowerOff: + GetHAL().remotePowerOff(); + break; + default: + break; + } + }); + GetHAL().onWsDanceData.connect([&](std::string_view data) { LvglLockGuard lvgl_lock; auto sequence = stackchan::animation::parse_sequence_from_json(data.data()); @@ -324,6 +342,7 @@ void AppAvatar::onClose() GetHAL().onWsCallRequest.clear(); GetHAL().onWsCallEnd.clear(); GetHAL().onWsTextMessage.clear(); + GetHAL().onWsDeviceControl.clear(); GetHAL().onWsDanceData.clear(); _ws_call_view_id = -1; diff --git a/firmware/main/apps/app_avatar/view/ws_display_card.cpp b/firmware/main/apps/app_avatar/view/ws_display_card.cpp index 72379231..dc21a974 100644 --- a/firmware/main/apps/app_avatar/view/ws_display_card.cpp +++ b/firmware/main/apps/app_avatar/view/ws_display_card.cpp @@ -51,7 +51,7 @@ WsDisplayCardView::WsDisplayCardView(lv_obj_t* parent, std::string title, const _title->align(LV_ALIGN_TOP_LEFT, 0, 0); _title->setWidth(260); _title->setText(title.empty() ? "STATUS" : title); - _title->setTextFont(&lv_font_montserrat_18); + _title->setTextFont(&lv_font_montserrat_20); _title->setTextColor(lv_color_hex(0x47330A)); _content = std::make_unique(_card->get()); diff --git a/firmware/main/hal/board/hal_bridge.h b/firmware/main/hal/board/hal_bridge.h index ee3f6bd0..f3c1d343 100644 --- a/firmware/main/hal/board/hal_bridge.h +++ b/firmware/main/hal/board/hal_bridge.h @@ -60,6 +60,8 @@ void board_set_backlight_brightness(uint8_t brightness, bool permanent = false); uint8_t board_get_backlight_brightness(); void board_set_speaker_volume(uint8_t volume, bool permanent = false); uint8_t board_get_speaker_volume(); +void board_set_power_save_mode(bool enabled); +void board_power_off(); void app_play_sound(const std::string_view& sound); diff --git a/firmware/main/hal/board/stackchan.cc b/firmware/main/hal/board/stackchan.cc index 7349b145..dfc0bd71 100644 --- a/firmware/main/hal/board/stackchan.cc +++ b/firmware/main/hal/board/stackchan.cc @@ -552,6 +552,26 @@ class M5StackCoreS3Board : public WifiBoard { return &backlight; } + void SetManualPowerSaveMode(bool enabled) + { + if (enabled) { + GetDisplay()->SetPowerSaveMode(true); + GetBacklight()->SetBrightness(10); + return; + } + + GetDisplay()->SetPowerSaveMode(false); + GetBacklight()->RestoreBrightness(); + if (power_save_timer_ != nullptr) { + power_save_timer_->WakeUp(); + } + } + + void PowerOffNow() + { + pmic_->PowerOff(); + } + i2c_master_bus_handle_t GetI2cBus() { return i2c_bus_; @@ -650,6 +670,18 @@ uint8_t hal_bridge::board_get_speaker_volume() return volume; } +void hal_bridge::board_set_power_save_mode(bool enabled) +{ + auto& board = (M5StackCoreS3Board&)Board::GetInstance(); + board.SetManualPowerSaveMode(enabled); +} + +void hal_bridge::board_power_off() +{ + auto& board = (M5StackCoreS3Board&)Board::GetInstance(); + board.PowerOffNow(); +} + void hal_bridge::toggle_xiaozhi_chat_state() { auto& app = Application::GetInstance(); diff --git a/firmware/main/hal/hal.cpp b/firmware/main/hal/hal.cpp index fded31db..f56dd7d5 100644 --- a/firmware/main/hal/hal.cpp +++ b/firmware/main/hal/hal.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: MIT */ #include "hal.h" +#include "board/hal_bridge.h" #include #include #include @@ -87,6 +88,21 @@ void Hal::reboot() esp_restart(); } +void Hal::enterRemoteSleepMode() +{ + hal_bridge::board_set_power_save_mode(true); +} + +void Hal::exitRemoteSleepMode() +{ + hal_bridge::board_set_power_save_mode(false); +} + +void Hal::remotePowerOff() +{ + hal_bridge::board_power_off(); +} + static void _confirm_ota_image_if_stable() { constexpr uint32_t ota_confirm_delay_ms = 20000; diff --git a/firmware/main/hal/hal.h b/firmware/main/hal/hal.h index ea489ab7..18f4073e 100644 --- a/firmware/main/hal/hal.h +++ b/firmware/main/hal/hal.h @@ -37,6 +37,12 @@ enum class WsTextMessageMode { DisplayCard, }; +enum class WsDeviceControlAction { + Sleep = 0, + Wake, + PowerOff, +}; + /** * @brief * @@ -51,6 +57,10 @@ struct WsTextMessage_t { uint32_t durationMs = 4000; }; +struct WsDeviceControl_t { + WsDeviceControlAction action = WsDeviceControlAction::Sleep; +}; + /** * @brief * @@ -199,6 +209,9 @@ class Hal { std::array getFactoryMac(); std::string getFactoryMacString(std::string divider = ""); void reboot(); + void enterRemoteSleepMode(); + void exitRemoteSleepMode(); + void remotePowerOff(); void updateHeapStatusLog(); uint8_t getBatteryLevel(); bool isBatteryCharging(); @@ -256,6 +269,7 @@ class Hal { uitk::Signal onWsCallResponse; uitk::Signal onWsCallEnd; uitk::Signal onWsTextMessage; + uitk::Signal onWsDeviceControl; uitk::Signal onWsVideoModeChange; uitk::Signal> onWsVideoFrame; uitk::Signal onWsDanceData; diff --git a/firmware/main/hal/hal_ws_avatar.cpp b/firmware/main/hal/hal_ws_avatar.cpp index 731bd34f..858b6202 100644 --- a/firmware/main/hal/hal_ws_avatar.cpp +++ b/firmware/main/hal/hal_ws_avatar.cpp @@ -295,6 +295,28 @@ class WebSocketAvatar { auto type = doc["type"].as(); if (type == "displayCard" || type == "display_card") { text_msg.mode = WsTextMessageMode::DisplayCard; + } else if (type == "deviceControl" || type == "device_control") { + WsDeviceControl_t control_msg; + + if (!doc["action"].is()) { + ESP_LOGE(_tag.c_str(), "deviceControl action missing"); + return; + } + + auto action = doc["action"].as(); + if (action == "sleep") { + control_msg.action = WsDeviceControlAction::Sleep; + } else if (action == "wake") { + control_msg.action = WsDeviceControlAction::Wake; + } else if (action == "powerOff" || action == "power_off" || action == "shutdown") { + control_msg.action = WsDeviceControlAction::PowerOff; + } else { + ESP_LOGE(_tag.c_str(), "unknown deviceControl action: %s", action.c_str()); + return; + } + + GetHAL().onWsDeviceControl.emit(control_msg); + return; } }