/* * SPDX-FileCopyrightText: 2026 M5Stack Technology CO LTD * * SPDX-License-Identifier: MIT */ #include "hal.h" #include #include #include static std::unique_ptr _hal_instance; static const std::string_view _tag = "HAL"; Hal& GetHAL() { if (!_hal_instance) { mclog::tagInfo(_tag, "creating hal instance"); _hal_instance = std::make_unique(); } return *_hal_instance.get(); } void Hal::init() { mclog::tagInfo(_tag, "init"); // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { ESP_ERROR_CHECK(nvs_flash_erase()); ret = nvs_flash_init(); } ESP_ERROR_CHECK(ret); xiaozhi_board_init(); xiaozhi_mcp_init(); head_touch_init(); io_expander_init(); rtc_init(); imu_init(); servo_init(); lvgl_init(); } /* -------------------------------------------------------------------------- */ /* System */ /* -------------------------------------------------------------------------- */ #include #include #include #include #include #include #include void Hal::delay(std::uint32_t ms) { vTaskDelay(pdMS_TO_TICKS(ms)); } std::uint32_t Hal::millis() { return esp_timer_get_time() / 1000; } void Hal::feedTheDog() { vTaskDelay(1); } std::array Hal::getFactoryMac() { std::array mac; esp_efuse_mac_get_default(mac.data()); return mac; } std::string Hal::getFactoryMacString(std::string divider) { auto mac = getFactoryMac(); return fmt::format("{:02X}{}{:02X}{}{:02X}{}{:02X}{}{:02X}{}{:02X}", mac[0], divider, mac[1], divider, mac[2], divider, mac[3], divider, mac[4], divider, mac[5]); } void Hal::reboot() { esp_restart(); } static void _confirm_ota_image_if_stable() { constexpr uint32_t ota_confirm_delay_ms = 20000; static bool ota_confirm_checked = false; if (ota_confirm_checked || GetHAL().millis() < ota_confirm_delay_ms) { return; } ota_confirm_checked = true; const esp_partition_t* running = esp_ota_get_running_partition(); if (running == nullptr) { mclog::tagError(_tag, "failed to get running partition for ota confirmation"); return; } esp_ota_img_states_t ota_state; if (esp_ota_get_state_partition(running, &ota_state) != ESP_OK) { mclog::tagError(_tag, "failed to get ota state for partition: {}", running->label); return; } mclog::tagInfo(_tag, "ota confirm check: partition={}, state={}", running->label, static_cast(ota_state)); if (ota_state == ESP_OTA_IMG_PENDING_VERIFY) { mclog::tagInfo(_tag, "ota image is stable, marking current app valid"); esp_ota_mark_app_valid_cancel_rollback(); } } void Hal::updateHeapStatusLog() { _confirm_ota_image_if_stable(); static uint32_t last_log_tick = 0; if (millis() - last_log_tick < 10000) { return; } last_log_tick = millis(); SystemInfo::PrintHeapStats(); } /* -------------------------------------------------------------------------- */ /* Xiaozhi */ /* -------------------------------------------------------------------------- */ #include "board/hal_bridge.h" #include #include #include void Hal::xiaozhi_board_init() { mclog::tagInfo(_tag, "xiaozhi board init"); hal_bridge::xiaozhi_board_init(); } static void _stackchan_update_task(void* param) { bool is_xiaozhi_ready = false; bool is_setup_done = false; while (1) { vTaskDelay(pdMS_TO_TICKS(10)); tools::update_reminders(); LvglLockGuard lock; GetStackChan().update(); if (!is_xiaozhi_ready) { is_xiaozhi_ready = hal_bridge::is_xiaozhi_ready(); continue; } if (!is_setup_done) { // Setup when xiaozhi ready GetHAL().startSntp(); view::create_home_indicator([]() { GetHAL().requestWarmReboot(0); }, 0x81DBBD, 0x134233); view::create_status_bar(0x81DBBD, 0x134233); is_setup_done = true; } view::update_home_indicator(); view::update_status_bar(); } } void Hal::startXiaozhi() { mclog::tagInfo(_tag, "start xiaozhi"); auto& motion = GetStackChan().motion(); motion.setAutoAngleSyncEnabled(true); motion.setAutoTorqueReleaseEnabled(true); // Setup reminder handler tools::on_reminder_triggered().clear(); tools::on_reminder_triggered().connect([](int id, std::string_view msg) { mclog::tagInfo(_tag, "reminder triggered: id: {}, msg: {}", id, msg); { LvglLockGuard lock; auto& avatar = GetStackChan().avatar(); avatar.addDecorator(std::make_unique(lv_screen_active(), msg)); } hal_bridge::app_play_sound(OGG_NEW_NOTIFICATION); }); // Start stackchan update task xTaskCreatePinnedToCore(_stackchan_update_task, "stackchan", 4096, NULL, 5, NULL, 1); hal_bridge::start_xiaozhi_app(); } uint8_t Hal::getBatteryLevel() { return hal_bridge::board_get_battery_level(); } bool Hal::isBatteryCharging() { return hal_bridge::board_is_battery_charging(); } void Hal::factoryReset() { mclog::tagInfo(_tag, "start factory reset"); ESP_ERROR_CHECK(nvs_flash_erase()); reboot(); } /* -------------------------------------------------------------------------- */ /* Display */ /* -------------------------------------------------------------------------- */ #include "board/hal_bridge.h" void Hal::lvglLock() { hal_bridge::disply_lvgl_lock(); } void Hal::lvglUnlock() { hal_bridge::disply_lvgl_unlock(); } void Hal::setBackLightBrightness(uint8_t brightness, bool permanent) { hal_bridge::board_set_backlight_brightness(brightness, permanent); } uint8_t Hal::getBackLightBrightness() { return hal_bridge::board_get_backlight_brightness(); } void Hal::setSpeakerVolume(uint8_t volume, bool permanent) { hal_bridge::board_set_speaker_volume(volume, permanent); } uint8_t Hal::getSpeakerVolume() { return hal_bridge::board_get_speaker_volume(); } /* -------------------------------------------------------------------------- */ /* Lvgl */ /* -------------------------------------------------------------------------- */ #include "board/hal_bridge.h" #include static void lvgl_read_cb(lv_indev_t* indev, lv_indev_data_t* data) { hal_bridge::lock(); auto& bridge_data = hal_bridge::get_data(); // mclog::tagInfo(_tag, "touchpoint: {}, x: {}, y: {}", bridge_data.touchPoint.num, bridge_data.touchPoint.x, // bridge_data.touchPoint.y); if (bridge_data.touchPoint.num == 0) { data->state = LV_INDEV_STATE_RELEASED; } else { data->state = LV_INDEV_STATE_PRESSED; data->point.x = bridge_data.touchPoint.x; data->point.y = bridge_data.touchPoint.y; } hal_bridge::unlock(); } void Hal::lvgl_init() { mclog::tagInfo(_tag, "lvgl init"); hal_bridge::disply_lvgl_lock(); mclog::tagInfo(_tag, "create lvgl touchpad indev"); lvTouchpad = lv_indev_create(); lv_indev_set_type(lvTouchpad, LV_INDEV_TYPE_POINTER); lv_indev_set_read_cb(lvTouchpad, lvgl_read_cb); lv_indev_set_group(lvTouchpad, lv_group_get_default()); lv_indev_set_display(lvTouchpad, hal_bridge::display_get_lvgl_display()); hal_bridge::disply_lvgl_unlock(); } /* -------------------------------------------------------------------------- */ /* Warm Reboot */ /* -------------------------------------------------------------------------- */ #include #include static std::string_view _warm_boot_nvs_ns = "warm_boot"; static std::string_view _warm_boot_nvs_key = "app_index"; void Hal::requestWarmReboot(int appIndex) { mclog::tagInfo(_tag, "warm reboot request to app index: {}", appIndex); { Settings settings(_warm_boot_nvs_ns.data(), true); settings.SetInt(_warm_boot_nvs_key.data(), appIndex); } delay(100); esp_restart(); } int Hal::getWarmRebootTarget() { Settings settings(_warm_boot_nvs_ns.data(), false); return settings.GetInt(_warm_boot_nvs_key.data(), -1); } void Hal::clearWarmRebootRequest() { mclog::tagInfo(_tag, "clear warm reboot request"); Settings settings(_warm_boot_nvs_ns.data(), true); settings.SetInt(_warm_boot_nvs_key.data(), -1); }