From 400c5ed996a9dd4657f4f5860f4e60aa7b69f276 Mon Sep 17 00:00:00 2001 From: liu weikai Date: Fri, 26 Jun 2026 16:20:37 +0800 Subject: [PATCH] fix(esp): reduce RAM pressure and TDeck display stalls Tune Arduino task stacks and board env flags to recover internal RAM for BLE startup. Avoid synchronous boot UI rendering on Arduino loopTask and move large board singletons toward PSRAM where available. Improve TDeck/Pager display responsiveness by restoring practical LVGL DMA buffers, using SPI pixel writes, disabling transition animations, and testing TDeck display SPI at 80MHz. --- AGENTS.md | 23 +++ .../boards/t_display_p4/t_display_p4_board.h | 4 + boards/tab5/include/boards/tab5/tab5_board.h | 4 + boards/tdeck/src/tdeck_board.cpp | 35 +++- boards/tlora_pager/src/tlora_pager_board.cpp | 17 +- .../phone/meshcore/meshcore_phone_core.h | 7 + .../meshtastic/meshtastic_phone_session.h | 8 + .../src/meshcore/meshcore_phone_core.cpp | 25 +++ .../meshtastic/meshtastic_phone_session.cpp | 26 +++ .../src/packs/compatibility_ux_pack.cpp | 3 +- .../tests/test_compatibility_ux_pack.cpp | 6 +- .../map_tiles/map_tile_render_queue.h | 2 +- .../map/map_overlay_snapshot.h | 2 +- .../src/page/page_manifest.cpp | 1 + .../tests/test_page_manifest.cpp | 3 +- .../ui_shared/include/ui/menu/menu_profile.h | 1 + .../ui_shared/src/ui/app_catalog_builder.cpp | 17 +- modules/ui_shared/src/ui/app_runtime.cpp | 4 +- .../src/ui/assets/fonts/font_utils.cpp | 11 +- modules/ui_shared/src/ui/menu/menu_layout.cpp | 4 +- .../ui_shared/src/ui/menu/menu_profile.cpp | 2 + .../ui_shared/src/ui/runtime/ui_feedback.cpp | 9 +- .../energy_sweep_page_runtime.cpp | 17 +- .../gnss/gnss_skyplot_page_runtime.cpp | 15 +- .../src/ui/screens/gps/gps_page_runtime.cpp | 9 +- .../settings/settings_page_components.cpp | 193 +++++++++++++----- .../ui/screens/settings/settings_state.cpp | 11 +- modules/ui_shared/src/ui/startup_shell.cpp | 17 ++ modules/ui_shared/src/ui/startup_ui_shell.cpp | 17 ++ .../src/ui/widgets/map/map_viewport.cpp | 26 +++ .../arduino_common/include/ble/meshcore_ble.h | 7 +- .../include/ble/meshtastic_ble.h | 7 +- .../platform/esp/arduino_common/app_tasks.h | 5 +- .../arduino_common/chat/infra/contact_store.h | 6 + .../chat/infra/lxmf/lxmf_adapter.h | 5 + .../chat/infra/meshcore/meshcore_adapter.h | 6 + .../chat/infra/meshtastic/mt_adapter.h | 6 + .../chat/infra/meshtastic/node_store.h | 5 + .../chat/infra/rnode/rnode_adapter.h | 5 + .../mesh/esp_meshtastic_adapter_bridge.h | 6 + .../esp/arduino_common/src/LV_Helper_v9.cpp | 11 + .../esp/arduino_common/src/app_context.cpp | 19 +- .../src/app_context_platform_bindings.cpp | 10 +- platform/esp/arduino_common/src/app_tasks.cpp | 4 +- .../arduino_common/src/ble/meshcore_ble.cpp | 39 +++- .../arduino_common/src/ble/meshtastic_ble.cpp | 54 ++++- .../src/chat/infra/contact_store.cpp | 21 ++ .../src/chat/infra/lxmf/lxmf_adapter.cpp | 21 ++ .../chat/infra/meshcore/meshcore_adapter.cpp | 21 ++ .../src/chat/infra/meshtastic/mt_adapter.cpp | 21 ++ .../src/chat/infra/meshtastic/node_store.cpp | 23 ++- .../src/chat/infra/rnode/rnode_adapter.cpp | 21 ++ .../arduino_common/src/gps/gps_service.cpp | 23 ++- .../arduino_common/src/gps/track_recorder.cpp | 17 +- .../mesh/esp_meshtastic_adapter_bridge.cpp | 21 ++ .../src/ui/widgets/map/map_tiles.cpp | 38 +++- .../boards/src/display/DisplayInterface.cpp | 25 +-- .../src/platform_ui_sstv_runtime.cpp | 18 +- .../trail_mate_t_display_p4_runtime.cpp | 187 ++++++++++++++++- platformio.ini | 2 - .../lilygo_tlora_pager/envs/tlora_pager.ini | 51 +++++ .../envs/lilygo_twatch_s3.ini | 28 +++ variants/tdeck/envs/tdeck.ini | 34 ++- variants/tdeck_pro/envs/tdeck_pro.ini | 28 +++ 64 files changed, 1123 insertions(+), 191 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e82f1a55..125138fd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,3 +41,26 @@ This project is indexed by GitNexus as **trail-mate** (43886 symbols, 82034 rela | Index, status, clean, wiki CLI commands | `.claude/skills/gitnexus/gitnexus-cli/SKILL.md` | + +# Trail Mate Agent Rules + +## PlatformIO Builds, Uploads, And Monitors + +- When a PlatformIO build/upload is started, let it run until it explicitly completes with success or failure. Do not impose an arbitrary time limit such as 120s or 300s. +- Never treat a tool-call timeout as the build result. The only valid build result is the PlatformIO process exit code and its final log output. +- Never use a foreground `shell_command` call as the control mechanism for long PlatformIO builds. The shell tool timeout must not become a build timeout. +- For any long build, start it as a hidden background process, redirect stdout/stderr to a log file, record the PID and log path, and poll with short commands until the process exits. +- Run PlatformIO build, upload, or monitor when it is part of the requested verification, and make sure each started process reaches a definite terminal state. +- Before starting a PlatformIO build/upload/monitor, check for existing processes for the same repository and environment. Do not start a duplicate build. +- If a tool call times out, is interrupted, or ends unexpectedly while a build/upload is running, immediately check the recorded PID and any orphaned `pio`, compiler, linker, and `.pio/build/` processes before doing anything else. +- Stop only clearly matching stale processes for this repository/environment. Never use broad process kills. +- Keep serial monitoring short and bounded unless the user explicitly asks for a longer capture. +- Prefer CI/release-time artifacts for routine build outputs. Do not rebuild locally on every small change. + +## PowerShell Command Discipline + +- Keep PowerShell commands short and single-purpose. Avoid dense one-liners with complex quoting, nested shells, or long chained pipelines. +- Prefer native PowerShell cmdlets end to end. Do not mix PowerShell enumeration with `cmd /c` for file or process operations. +- Use `rg`/`rg --files` for search, `git` for git state, and `apply_patch` for manual file edits. +- For repeated or complex local operations, write down the intended steps first and run them as small observable commands instead of one large opaque command. +- After any failed or timed-out command, inspect the process state before retrying. diff --git a/boards/t_display_p4/include/boards/t_display_p4/t_display_p4_board.h b/boards/t_display_p4/include/boards/t_display_p4/t_display_p4_board.h index 3469b772..00867ef6 100644 --- a/boards/t_display_p4/include/boards/t_display_p4/t_display_p4_board.h +++ b/boards/t_display_p4/include/boards/t_display_p4/t_display_p4_board.h @@ -140,6 +140,10 @@ class TDisplayP4Board final : public BoardBase, public LoraBoard bool isCardReady() override; bool isGPSReady() const override; bool hasGPSHardware() const override { return hasGpsUart(); } + bool hasSstvAudioInput() const override + { + return hasAudio() && profile().audio_i2s.din >= 0 && hasSdCard(); + } void vibrator() override; void stopVibrator() override; diff --git a/boards/tab5/include/boards/tab5/tab5_board.h b/boards/tab5/include/boards/tab5/tab5_board.h index 04404ad7..c30e12b5 100644 --- a/boards/tab5/include/boards/tab5/tab5_board.h +++ b/boards/tab5/include/boards/tab5/tab5_board.h @@ -113,6 +113,10 @@ class Tab5Board final : public BoardBase, public LoraBoard bool isCardReady() override; bool isGPSReady() const override; bool hasGPSHardware() const override { return hasGpsUart(); } + bool hasSstvAudioInput() const override + { + return hasAudio() && audioI2sPins().din >= 0 && hasSdCard(); + } void vibrator() override; void stopVibrator() override; diff --git a/boards/tdeck/src/tdeck_board.cpp b/boards/tdeck/src/tdeck_board.cpp index 47781940..97e013ce 100644 --- a/boards/tdeck/src/tdeck_board.cpp +++ b/boards/tdeck/src/tdeck_board.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include namespace boards::tdeck @@ -30,6 +31,7 @@ constexpr float kTDeckRadioCurrentLimitMa = 140.0f; constexpr uint8_t kTDeckBacklightStepMax = 16; constexpr uint32_t kTDeckBacklightOffDelayMs = 3; constexpr uint32_t kTDeckBacklightWakeDelayUs = 30; +constexpr uint32_t kTDeckDisplaySpiClockMhz = 80; // LilyGo's T-Deck GPSShield reference starts the L76K/CASIC path at 9600. constexpr uint32_t kGpsDefaultBaud = 9600; constexpr uint8_t kGpsProfileAuto = 0; @@ -238,8 +240,19 @@ TDeckBoard::TDeckBoard() TDeckBoard* TDeckBoard::getInstance() { - static TDeckBoard instance; - return &instance; + static TDeckBoard* instance = []() -> TDeckBoard* + { + void* storage = heap_caps_malloc_prefer(sizeof(TDeckBoard), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(TDeckBoard)); + } + return new (storage) TDeckBoard(); + }(); + return instance; } bool TDeckBoard::initGPS() @@ -393,15 +406,25 @@ uint32_t TDeckBoard::begin(uint32_t disable_hw_init) // Initialize display (ST7789) before SD so the SPI lock exists (pager-style ordering). #if defined(DISP_SCK) && defined(DISP_MISO) && defined(DISP_MOSI) && defined(DISP_CS) && defined(DISP_DC) - // Match LilyGo's T-Deck reference setup: a 40 MHz display clock shortens LVGL flush time - // and reduces the visible scan/jelly effect during scrolling and animations. - LilyGoDispArduinoSPI::init(DISP_SCK, DISP_MISO, DISP_MOSI, DISP_CS, DISP_RST, DISP_DC, DISP_BL, 40, SPI); + // Keep T-Deck display flushes fast enough that LVGL partial refresh does not visibly scan. + LilyGoDispArduinoSPI::init(DISP_SCK, + DISP_MISO, + DISP_MOSI, + DISP_CS, + DISP_RST, + DISP_DC, + DISP_BL, + kTDeckDisplaySpiClockMhz, + SPI); // T-Deck default orientation should be rotated right by 90 degrees. LilyGoDispArduinoSPI::setRotation(1); rotation_ = LilyGoDispArduinoSPI::getRotation(); display_ready_ = true; setBrightness(brightness_); - Serial.printf("[TDeckBoard] display init OK: %ux%u\n", LilyGoDispArduinoSPI::_width, LilyGoDispArduinoSPI::_height); + Serial.printf("[TDeckBoard] display init OK: %ux%u spi=%luMHz\n", + LilyGoDispArduinoSPI::_width, + LilyGoDispArduinoSPI::_height, + static_cast(kTDeckDisplaySpiClockMhz)); #else Serial.println("[TDeckBoard] display init skipped: missing DISP_* pins"); #endif diff --git a/boards/tlora_pager/src/tlora_pager_board.cpp b/boards/tlora_pager/src/tlora_pager_board.cpp index 2ca19bfd..0e9d2c1c 100644 --- a/boards/tlora_pager/src/tlora_pager_board.cpp +++ b/boards/tlora_pager/src/tlora_pager_board.cpp @@ -11,8 +11,10 @@ #include #include #include +#include #include #include +#include #include "display/drivers/ST7796.h" #include "pins_arduino.h" @@ -223,8 +225,19 @@ TLoRaPagerBoard::~TLoRaPagerBoard() TLoRaPagerBoard* TLoRaPagerBoard::getInstance() { - static TLoRaPagerBoard instance; - return &instance; + static TLoRaPagerBoard* instance = []() -> TLoRaPagerBoard* + { + void* storage = heap_caps_malloc_prefer(sizeof(TLoRaPagerBoard), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(TLoRaPagerBoard)); + } + return new (storage) TLoRaPagerBoard(); + }(); + return instance; } void TLoRaPagerBoard::initShareSPIPins() diff --git a/modules/core_phone/include/phone/meshcore/meshcore_phone_core.h b/modules/core_phone/include/phone/meshcore/meshcore_phone_core.h index 469d5100..283f02ae 100644 --- a/modules/core_phone/include/phone/meshcore/meshcore_phone_core.h +++ b/modules/core_phone/include/phone/meshcore/meshcore_phone_core.h @@ -4,6 +4,7 @@ #include "phone/common/phone_app_facade.h" #include +#include #include #include #include @@ -154,6 +155,12 @@ class MeshCorePhoneHooks class MeshCorePhoneCore { public: +#if defined(ESP_PLATFORM) + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t) noexcept; +#endif + MeshCorePhoneCore(IPhoneAppFacade& app, const std::string& device_name, MeshCorePhoneHooks* hooks = nullptr); diff --git a/modules/core_phone/include/phone/meshtastic/meshtastic_phone_session.h b/modules/core_phone/include/phone/meshtastic/meshtastic_phone_session.h index d8bd72b6..46599e8e 100644 --- a/modules/core_phone/include/phone/meshtastic/meshtastic_phone_session.h +++ b/modules/core_phone/include/phone/meshtastic/meshtastic_phone_session.h @@ -2,12 +2,20 @@ #include "phone/meshtastic/meshtastic_phone_core.h" +#include + namespace phone::meshtastic { class MeshtasticPhoneSession { public: +#if defined(ESP_PLATFORM) + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t) noexcept; +#endif + MeshtasticPhoneSession(IPhoneAppFacade& app, MeshtasticPhoneTransport& transport, MeshtasticPhoneBluetoothConfigHooks* bluetooth_config_hooks, diff --git a/modules/core_phone/src/meshcore/meshcore_phone_core.cpp b/modules/core_phone/src/meshcore/meshcore_phone_core.cpp index b8433e2d..69047c92 100644 --- a/modules/core_phone/src/meshcore/meshcore_phone_core.cpp +++ b/modules/core_phone/src/meshcore/meshcore_phone_core.cpp @@ -6,6 +6,10 @@ #include #include #include +#if defined(ESP_PLATFORM) +#include "esp_heap_caps.h" +#include +#endif namespace phone::meshcore { @@ -166,6 +170,27 @@ uint32_t nowSeconds() } // namespace +#if defined(ESP_PLATFORM) +void* MeshCorePhoneCore::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MeshCorePhoneCore::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MeshCorePhoneCore::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} +#endif + MeshCorePhoneCore::MeshCorePhoneCore(IPhoneAppFacade& app, const std::string& device_name, MeshCorePhoneHooks* hooks) : app_(app), device_name_(device_name), hooks_(hooks) {} diff --git a/modules/core_phone/src/meshtastic/meshtastic_phone_session.cpp b/modules/core_phone/src/meshtastic/meshtastic_phone_session.cpp index 01592850..295a3836 100644 --- a/modules/core_phone/src/meshtastic/meshtastic_phone_session.cpp +++ b/modules/core_phone/src/meshtastic/meshtastic_phone_session.cpp @@ -1,8 +1,34 @@ #include "phone/meshtastic/meshtastic_phone_session.h" +#if defined(ESP_PLATFORM) +#include "esp_heap_caps.h" +#include +#endif + namespace phone::meshtastic { +#if defined(ESP_PLATFORM) +void* MeshtasticPhoneSession::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MeshtasticPhoneSession::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MeshtasticPhoneSession::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} +#endif + MeshtasticPhoneSession::MeshtasticPhoneSession(IPhoneAppFacade& app, MeshtasticPhoneTransport& transport, MeshtasticPhoneBluetoothConfigHooks* bluetooth_config_hooks, MeshtasticPhoneModuleConfigHooks* module_config_hooks, diff --git a/modules/ui_lvgl_ux_packs/src/packs/compatibility_ux_pack.cpp b/modules/ui_lvgl_ux_packs/src/packs/compatibility_ux_pack.cpp index 47831a1f..1a49f667 100644 --- a/modules/ui_lvgl_ux_packs/src/packs/compatibility_ux_pack.cpp +++ b/modules/ui_lvgl_ux_packs/src/packs/compatibility_ux_pack.cpp @@ -35,7 +35,7 @@ const UxFeatureSet& CompatibilityUxPack::features() const true, true, true, - true, + false, true, }; return features; @@ -53,7 +53,6 @@ void CompatibilityUxPack::buildScreens(ScreenRegistry& out) const (void)out.add({ScreenId::Tracker, "Tracker", true}); (void)out.add({ScreenId::Settings, "Settings", true}); (void)out.add({ScreenId::WalkieTalkie, "Walkie", true}); - (void)out.add({ScreenId::Sstv, "SSTV", true}); (void)out.add({ScreenId::Extensions, "Extensions", true}); } diff --git a/modules/ui_lvgl_ux_packs/tests/test_compatibility_ux_pack.cpp b/modules/ui_lvgl_ux_packs/tests/test_compatibility_ux_pack.cpp index aeae0bc9..b9a32ffd 100644 --- a/modules/ui_lvgl_ux_packs/tests/test_compatibility_ux_pack.cpp +++ b/modules/ui_lvgl_ux_packs/tests/test_compatibility_ux_pack.cpp @@ -11,15 +11,15 @@ int main() assert(pack.profile().input_model == ui_lvgl_ux::InputModel::ButtonsOnly); assert(pack.features().chat); assert(pack.features().walkie); - assert(pack.features().sstv); + assert(!pack.features().sstv); assert(pack.features().extensions); ui_lvgl_ux::ScreenRegistry screens; pack.buildScreens(screens); - assert(screens.size() == 11); + assert(screens.size() == 10); assert(screens.items()[0].id == ui_lvgl_ux::ScreenId::Dashboard); assert(screens.items()[1].id == ui_lvgl_ux::ScreenId::Chat); - assert(screens.items()[10].id == ui_lvgl_ux::ScreenId::Extensions); + assert(screens.items()[9].id == ui_lvgl_ux::ScreenId::Extensions); ui_lvgl_ux::InputBindingSet bindings; pack.buildInputBindings(bindings); diff --git a/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_render_queue.h b/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_render_queue.h index f21a6bc7..3d5d0ef6 100644 --- a/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_render_queue.h +++ b/modules/ui_map_runtime/include/ui_map_runtime/map_tiles/map_tile_render_queue.h @@ -38,7 +38,7 @@ struct MapTileRenderRef class MapTileRenderQueue { public: - static constexpr std::size_t kMaxTiles = 64; + static constexpr std::size_t kMaxTiles = 32; void clear(); bool push(const MapTileRenderRef& item); diff --git a/modules/ui_presentation/include/ui_presentation/map/map_overlay_snapshot.h b/modules/ui_presentation/include/ui_presentation/map/map_overlay_snapshot.h index 2d72fb88..afa8ae4a 100644 --- a/modules/ui_presentation/include/ui_presentation/map/map_overlay_snapshot.h +++ b/modules/ui_presentation/include/ui_presentation/map/map_overlay_snapshot.h @@ -59,7 +59,7 @@ struct MapOverlaySnapshot { ui::SnapshotHeader header; - static constexpr std::size_t kMaxItems = 64; + static constexpr std::size_t kMaxItems = 32; MapOverlayItem items[kMaxItems]{}; std::size_t item_count = 0; diff --git a/modules/ui_presentation/src/page/page_manifest.cpp b/modules/ui_presentation/src/page/page_manifest.cpp index a8ca09e9..0d327b69 100644 --- a/modules/ui_presentation/src/page/page_manifest.cpp +++ b/modules/ui_presentation/src/page/page_manifest.cpp @@ -15,6 +15,7 @@ constexpr PageManifestItem kTab5TouchItems[] = { {PageId::Team, "team", true, true}, {PageId::Tracker, "tracker", true, true}, {PageId::Contacts, "contacts", true, true}, + {PageId::Sstv, "sstv", true, true}, {PageId::Settings, "settings", true, true}, }; diff --git a/modules/ui_presentation/tests/test_page_manifest.cpp b/modules/ui_presentation/tests/test_page_manifest.cpp index 06bc09b1..0fd27f04 100644 --- a/modules/ui_presentation/tests/test_page_manifest.cpp +++ b/modules/ui_presentation/tests/test_page_manifest.cpp @@ -25,9 +25,10 @@ int main() { const auto* tab5 = ui::presentation::findPageManifest("tab5_touch_manifest"); assert(tab5 != nullptr); - assert(tab5->item_count >= 8); + assert(tab5->item_count >= 9); assert(contains(*tab5, ui::presentation::PageId::Map)); assert(contains(*tab5, ui::presentation::PageId::Contacts)); + assert(contains(*tab5, ui::presentation::PageId::Sstv)); const auto* t_display_p4 = ui::presentation::findPageManifest("t_display_p4_touch_manifest"); assert(t_display_p4 != nullptr); diff --git a/modules/ui_shared/include/ui/menu/menu_profile.h b/modules/ui_shared/include/ui/menu/menu_profile.h index 1b16ba5a..74e2a805 100644 --- a/modules/ui_shared/include/ui/menu/menu_profile.h +++ b/modules/ui_shared/include/ui/menu/menu_profile.h @@ -85,6 +85,7 @@ struct MenuLayoutProfile bool show_memory_stats = true; bool scroll_card_label = false; bool large_touch_hitbox = false; + bool transition_animation = true; const lv_font_t* card_label_font = &lv_font_montserrat_14; const lv_font_t* desc_font = &lv_font_montserrat_14; diff --git a/modules/ui_shared/src/ui/app_catalog_builder.cpp b/modules/ui_shared/src/ui/app_catalog_builder.cpp index 19ceb903..ad37d185 100644 --- a/modules/ui_shared/src/ui/app_catalog_builder.cpp +++ b/modules/ui_shared/src/ui/app_catalog_builder.cpp @@ -23,6 +23,7 @@ #include "ui/screens/settings/settings_page_shell.h" #if !defined(TRAIL_MATE_ENABLE_SSTV) || TRAIL_MATE_ENABLE_SSTV #include "ui/screens/sstv/sstv_page_shell.h" +#include "ui/screens/sstv/sstv_page_runtime.h" #endif #if !defined(GAT562_NO_TEAM) || !GAT562_NO_TEAM #include "ui/screens/team/team_page_shell.h" @@ -150,6 +151,18 @@ AppScreen* s_apps[kMaxMenuApps] = {}; ui::StaticAppCatalogState s_catalog_state = ui::makeStaticAppCatalogState(s_apps); ui::AppCatalog s_catalog = ui::makeStaticAppCatalog(&s_catalog_state); +#if !defined(TRAIL_MATE_ENABLE_SSTV) || TRAIL_MATE_ENABLE_SSTV +bool sstv_available() +{ + return sstv_page::ui::runtime::is_available(); +} +#else +bool sstv_available() +{ + return false; +} +#endif + } // namespace namespace ui::app_catalog_builder @@ -191,7 +204,7 @@ AppCatalog build(const FeatureFlags& flags) add(&s_pc_link_app); #endif } - if (flags.profile == CatalogProfile::PioDefault && flags.include_sstv) + if (flags.profile == CatalogProfile::PioDefault && flags.include_sstv && sstv_available()) { #if !defined(TRAIL_MATE_ENABLE_SSTV) || TRAIL_MATE_ENABLE_SSTV add(&s_sstv_app); @@ -211,7 +224,7 @@ AppCatalog build(const FeatureFlags& flags) add(&s_usb_app); #endif } - if (flags.profile == CatalogProfile::IdfDefault && flags.include_sstv) + if (flags.profile == CatalogProfile::IdfDefault && flags.include_sstv && sstv_available()) { #if !defined(TRAIL_MATE_ENABLE_SSTV) || TRAIL_MATE_ENABLE_SSTV add(&s_sstv_app); diff --git a/modules/ui_shared/src/ui/app_runtime.cpp b/modules/ui_shared/src/ui/app_runtime.cpp index 195d5394..ac9e0398 100644 --- a/modules/ui_shared/src/ui/app_runtime.cpp +++ b/modules/ui_shared/src/ui/app_runtime.cpp @@ -206,9 +206,7 @@ void install_app_edge_swipe(lv_indev_t* indev) lv_anim_enable_t transition_anim() { - return ui::menu_profile::current().input_mode == ui::menu_profile::InputMode::TouchPrimary - ? LV_ANIM_OFF - : LV_ANIM_ON; + return ui::menu_profile::current().transition_animation ? LV_ANIM_ON : LV_ANIM_OFF; } void show_menu_internal() diff --git a/modules/ui_shared/src/ui/assets/fonts/font_utils.cpp b/modules/ui_shared/src/ui/assets/fonts/font_utils.cpp index fcd3c7f6..d2a22d51 100644 --- a/modules/ui_shared/src/ui/assets/fonts/font_utils.cpp +++ b/modules/ui_shared/src/ui/assets/fonts/font_utils.cpp @@ -1,12 +1,19 @@ #include "ui/assets/fonts/font_utils.h" +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_FONT_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_FONT_STATE_RAM_ATTR +#endif + namespace ui::fonts { namespace { -constexpr std::size_t kMaxLocalizedFontBindings = 64; -LocalizedFontBinding s_localized_font_bindings[kMaxLocalizedFontBindings]{}; +constexpr std::size_t kMaxLocalizedFontBindings = 24; +UI_FONT_STATE_RAM_ATTR LocalizedFontBinding s_localized_font_bindings[kMaxLocalizedFontBindings]{}; } // namespace diff --git a/modules/ui_shared/src/ui/menu/menu_layout.cpp b/modules/ui_shared/src/ui/menu/menu_layout.cpp index 8aa9beba..2d8059ef 100644 --- a/modules/ui_shared/src/ui/menu/menu_layout.cpp +++ b/modules/ui_shared/src/ui/menu/menu_layout.cpp @@ -349,9 +349,7 @@ void syncFocusedDescLabel() lv_anim_enable_t transition_anim() { - return ui::menu_profile::current().input_mode == ui::menu_profile::InputMode::TouchPrimary - ? LV_ANIM_OFF - : LV_ANIM_ON; + return ui::menu_profile::current().transition_animation ? LV_ANIM_ON : LV_ANIM_OFF; } void enterPendingApp(void* user_data) diff --git a/modules/ui_shared/src/ui/menu/menu_profile.cpp b/modules/ui_shared/src/ui/menu/menu_profile.cpp index 7b71bbac..b9baa39b 100644 --- a/modules/ui_shared/src/ui/menu/menu_profile.cpp +++ b/modules/ui_shared/src/ui/menu/menu_profile.cpp @@ -77,6 +77,7 @@ MenuLayoutProfile make_tdeck_profile() profile.show_desc_label = false; profile.show_node_id = true; profile.large_touch_hitbox = false; + profile.transition_animation = false; profile.card_label_font = &lv_font_montserrat_12; profile.desc_font = &lv_font_montserrat_14; profile.node_id_font = &lv_font_montserrat_14; @@ -128,6 +129,7 @@ MenuLayoutProfile make_pager_profile() profile.show_desc_label = true; profile.show_node_id = true; profile.large_touch_hitbox = false; + profile.transition_animation = false; profile.card_label_font = &lv_font_montserrat_14; profile.desc_font = &lv_font_montserrat_14; profile.node_id_font = &lv_font_montserrat_14; diff --git a/modules/ui_shared/src/ui/runtime/ui_feedback.cpp b/modules/ui_shared/src/ui/runtime/ui_feedback.cpp index c0af564f..c05dce18 100644 --- a/modules/ui_shared/src/ui/runtime/ui_feedback.cpp +++ b/modules/ui_shared/src/ui/runtime/ui_feedback.cpp @@ -7,6 +7,13 @@ #include #include +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_FEEDBACK_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_FEEDBACK_STATE_RAM_ATTR +#endif + namespace ui::feedback { namespace @@ -121,7 +128,7 @@ class RuntimeFeedbackPresenter final : public sys::runtime::IFeedbackPresenter } }; -sys::runtime::FeedbackQueue s_feedback_queue; +UI_FEEDBACK_STATE_RAM_ATTR sys::runtime::FeedbackQueue s_feedback_queue; sys::runtime::DefaultFeedbackPolicy s_feedback_policy; RuntimeFeedbackEventSink s_feedback_events; RuntimeFeedbackPresenter s_feedback_presenter; diff --git a/modules/ui_shared/src/ui/screens/energy_sweep/energy_sweep_page_runtime.cpp b/modules/ui_shared/src/ui/screens/energy_sweep/energy_sweep_page_runtime.cpp index 9ca45550..f7462e2b 100644 --- a/modules/ui_shared/src/ui/screens/energy_sweep/energy_sweep_page_runtime.cpp +++ b/modules/ui_shared/src/ui/screens/energy_sweep/energy_sweep_page_runtime.cpp @@ -34,6 +34,13 @@ #define lv_font_montserrat_20 lv_font_montserrat_14 #endif +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_ENERGY_SWEEP_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_ENERGY_SWEEP_STATE_RAM_ATTR +#endif + using Host = energy_sweep::ui::shell::Host; namespace @@ -226,11 +233,11 @@ struct SweepBandPlan int bin_count = static_cast(((kDefaultFreqEndMhz - kDefaultFreqStartMhz) / kStepQuantMhz) + 0.5f) + 1; }; -EnergySweepUi s_ui; -SweepState s_state; -RadioContext s_radio; -SweepBandPlan s_band; -EnergySweepLayout s_layout; +UI_ENERGY_SWEEP_STATE_RAM_ATTR EnergySweepUi s_ui; +UI_ENERGY_SWEEP_STATE_RAM_ATTR SweepState s_state; +UI_ENERGY_SWEEP_STATE_RAM_ATTR RadioContext s_radio; +UI_ENERGY_SWEEP_STATE_RAM_ATTR SweepBandPlan s_band; +UI_ENERGY_SWEEP_STATE_RAM_ATTR EnergySweepLayout s_layout; lv_timer_t* s_refresh_timer = nullptr; EnergySweepLayout make_classic_layout() diff --git a/modules/ui_shared/src/ui/screens/gnss/gnss_skyplot_page_runtime.cpp b/modules/ui_shared/src/ui/screens/gnss/gnss_skyplot_page_runtime.cpp index c63b916e..18656cd2 100644 --- a/modules/ui_shared/src/ui/screens/gnss/gnss_skyplot_page_runtime.cpp +++ b/modules/ui_shared/src/ui/screens/gnss/gnss_skyplot_page_runtime.cpp @@ -13,6 +13,13 @@ #include #include +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_GNSS_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_GNSS_STATE_RAM_ATTR +#endif + #if !defined(LV_FONT_MONTSERRAT_12) || !LV_FONT_MONTSERRAT_12 #define lv_font_montserrat_12 lv_font_montserrat_14 #endif @@ -597,13 +604,13 @@ struct SkyPlotUi lv_point_precise_t ew_points[2]{}; }; -static SkyPlotUi s_ui{}; -static SkyPlotLayout s_layout{}; +static UI_GNSS_STATE_RAM_ATTR SkyPlotUi s_ui{}; +static UI_GNSS_STATE_RAM_ATTR SkyPlotLayout s_layout{}; static lv_timer_t* s_refresh_timer = nullptr; -static SatInfo s_cached_sats[kMaxSats]; +static UI_GNSS_STATE_RAM_ATTR SatInfo s_cached_sats[kMaxSats]; static int s_cached_sat_count = 0; -static GnssStatus s_cached_status{}; +static UI_GNSS_STATE_RAM_ATTR GnssStatus s_cached_status{}; static bool s_cached_status_valid = false; lv_color_t sys_color(SatInfo::Sys sys) diff --git a/modules/ui_shared/src/ui/screens/gps/gps_page_runtime.cpp b/modules/ui_shared/src/ui/screens/gps/gps_page_runtime.cpp index 297805be..438adf8c 100644 --- a/modules/ui_shared/src/ui/screens/gps/gps_page_runtime.cpp +++ b/modules/ui_shared/src/ui/screens/gps/gps_page_runtime.cpp @@ -49,6 +49,13 @@ using Projection = gps::ui::shell::Projection; #define lv_font_montserrat_12 lv_font_montserrat_14 #endif +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_GPS_PAGE_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_GPS_PAGE_STATE_RAM_ATTR +#endif + bool isGPSLoadingTiles() { return false; @@ -111,7 +118,7 @@ int s_map_zoom = kCardputerZeroMapDefaultZoom; int s_map_pan_x = 0; int s_map_pan_y = 0; bool s_map_view_initialized = false; -::ui::map::MapOverlaySnapshot s_overlay_snapshot; +UI_GPS_PAGE_STATE_RAM_ATTR ::ui::map::MapOverlaySnapshot s_overlay_snapshot; Projection s_projection = Projection::Map; lv_obj_t* s_gps_status_label = nullptr; lv_obj_t* s_gps_coord_label = nullptr; diff --git a/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp b/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp index dfa3c7a7..38666e35 100644 --- a/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp +++ b/modules/ui_shared/src/ui/screens/settings/settings_page_components.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -57,6 +58,7 @@ #endif #if defined(ESP_PLATFORM) +#include "esp_heap_caps.h" #include "esp_log.h" #endif @@ -82,13 +84,19 @@ namespace wifi_runtime = ::platform::ui::wifi; constexpr size_t kMaxItems = 32; constexpr size_t kMaxOptions = 40; -constexpr size_t kMaxWifiNetworks = 24; +constexpr size_t kMaxWifiNetworks = 8; +constexpr size_t kChatRegionOptionCapacity = 32; +constexpr size_t kMeshCoreRegionPresetOptionCapacity = 32; +constexpr size_t kLocaleOptionCapacity = 16; +constexpr size_t kTimeZoneOptionCapacity = 32; constexpr const char* kPrefsNs = "settings"; constexpr int kChatContactAlertsNone = 0; constexpr int kChatContactAlertsContacts = 1; constexpr int kChatContactAlertsAll = 2; constexpr int kNetTxPowerMin = app::AppConfig::kTxPowerMinDbm; constexpr int kNetTxPowerMax = app::AppConfig::kTxPowerMaxDbm; +constexpr size_t kTxPowerOptionCapacity = + static_cast(kNetTxPowerMax - kNetTxPowerMin + 1); constexpr int kGpsInitProbeMinMs = 250; constexpr int kGpsInitProbeMaxMs = 1600; @@ -113,6 +121,42 @@ struct ImeToggleClick lv_obj_t* state_label = nullptr; }; +struct DynamicOptionStorage +{ + settings::ui::SettingOption chat_region_options[kChatRegionOptionCapacity] = {}; + settings::ui::SettingOption meshcore_region_preset_options[kMeshCoreRegionPresetOptionCapacity] = {}; + settings::ui::SettingOption tx_power_options[kTxPowerOptionCapacity] = {}; + char tx_power_labels[kTxPowerOptionCapacity][12] = {}; + settings::ui::SettingOption locale_options[kLocaleOptionCapacity] = {}; + char locale_option_labels[kLocaleOptionCapacity][48] = {}; + settings::ui::SettingOption time_zone_options[kTimeZoneOptionCapacity] = {}; + char custom_time_zone_label[32] = {}; + settings::ui::SettingOption wifi_network_options[kMaxWifiNetworks] = {}; + char wifi_network_option_labels[kMaxWifiNetworks][64] = {}; + wifi_runtime::ScanResult wifi_scan_results[kMaxWifiNetworks] = {}; +}; + +DynamicOptionStorage& dynamic_options() +{ + static DynamicOptionStorage* storage = []() -> DynamicOptionStorage* + { +#if defined(ESP_PLATFORM) + void* ptr = heap_caps_malloc_prefer(sizeof(DynamicOptionStorage), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (ptr == nullptr) + { + ptr = ::operator new(sizeof(DynamicOptionStorage)); + } + return new (ptr) DynamicOptionStorage(); +#else + return new DynamicOptionStorage(); +#endif + }(); + return *storage; +} + gps_runtime::GpsReceiverInitConfig make_gps_receiver_init_config(const app::AppConfig& config) { gps_runtime::GpsReceiverInitConfig init{}; @@ -134,23 +178,12 @@ static lv_group_t* s_modal_prev_group = nullptr; static int s_pending_category = -1; static bool s_category_update_scheduled = false; static bool s_building_list = false; -static settings::ui::SettingOption kChatRegionOptions[32] = {}; static size_t kChatRegionOptionCount = 0; -static settings::ui::SettingOption kMeshCoreRegionPresetOptions[32] = {}; static size_t kMeshCoreRegionPresetOptionCount = 0; -static settings::ui::SettingOption kTxPowerOptions[64] = {}; static size_t kTxPowerOptionCount = 0; -static char kTxPowerLabels[64][12] = {}; -static settings::ui::SettingOption kLocaleOptions[16] = {}; static size_t kLocaleOptionCount = 0; -static char kLocaleOptionLabels[16][48] = {}; -static settings::ui::SettingOption kTimeZoneOptions[32] = {}; static size_t kTimeZoneOptionCount = 0; -static char kCustomTimeZoneLabel[32] = {}; -static settings::ui::SettingOption kWifiNetworkOptions[kMaxWifiNetworks] = {}; static size_t kWifiNetworkOptionCount = 0; -static char kWifiNetworkOptionLabels[kMaxWifiNetworks][64] = {}; -static wifi_runtime::ScanResult kWifiScanResults[kMaxWifiNetworks] = {}; static lv_timer_t* s_firmware_update_timer = nullptr; static bool s_firmware_overlay_owned = false; static firmware_update_runtime::Phase s_last_firmware_phase = firmware_update_runtime::Phase::Unsupported; @@ -177,15 +210,16 @@ static bool apply_settings_bool_patch(const char* key, bool value) static void refresh_timezone_options() { + auto& options = dynamic_options(); size_t profile_count = 0; const auto* profiles = ::platform::ui::time::timezone_profiles(&profile_count); kTimeZoneOptionCount = 0; - const size_t limit = sizeof(kTimeZoneOptions) / sizeof(kTimeZoneOptions[0]); + const size_t limit = kTimeZoneOptionCapacity; for (size_t i = 0; profiles && i < profile_count && kTimeZoneOptionCount < limit; ++i) { - kTimeZoneOptions[kTimeZoneOptionCount++] = {profiles[i].label, profiles[i].id}; + options.time_zone_options[kTimeZoneOptionCount++] = {profiles[i].label, profiles[i].id}; } - kCustomTimeZoneLabel[0] = '\0'; + options.custom_time_zone_label[0] = '\0'; } static void append_custom_timezone_option_if_needed(int profile_id, int offset_min) @@ -194,18 +228,19 @@ static void append_custom_timezone_option_if_needed(int profile_id, int offset_m { return; } - if (kTimeZoneOptionCount >= sizeof(kTimeZoneOptions) / sizeof(kTimeZoneOptions[0])) + auto& options = dynamic_options(); + if (kTimeZoneOptionCount >= kTimeZoneOptionCapacity) { return; } char tzdef[24] = {}; ::platform::ui::time::build_fixed_posix_tzdef(offset_min, tzdef, sizeof(tzdef)); - std::snprintf(kCustomTimeZoneLabel, - sizeof(kCustomTimeZoneLabel), + std::snprintf(options.custom_time_zone_label, + sizeof(options.custom_time_zone_label), "Fixed %s", tzdef[0] != '\0' ? tzdef : "UTC"); - kTimeZoneOptions[kTimeZoneOptionCount++] = {kCustomTimeZoneLabel, profile_id}; + options.time_zone_options[kTimeZoneOptionCount++] = {options.custom_time_zone_label, profile_id}; } static void update_item_value(settings::ui::ItemWidget& widget); @@ -218,6 +253,7 @@ static bool option_labels_are_translated(const settings::ui::SettingItem& item); static bool option_labels_use_content_font(const settings::ui::SettingItem& item); static void apply_locale_preview_font(lv_obj_t* label, const settings::ui::SettingItem& item, int value); static void refresh_language_pack_options(); +static void bind_dynamic_option_storage_to_items(); static void copy_bounded(char* out, size_t out_len, const char* text) { @@ -302,12 +338,13 @@ static void format_enabled_ime_summary(char* out, size_t out_len) static void clear_wifi_scan_options() { + auto& options = dynamic_options(); kWifiNetworkOptionCount = 0; for (size_t i = 0; i < kMaxWifiNetworks; ++i) { - kWifiNetworkOptions[i] = settings::ui::SettingOption{}; - kWifiNetworkOptionLabels[i][0] = '\0'; - kWifiScanResults[i] = wifi_runtime::ScanResult{}; + options.wifi_network_options[i] = settings::ui::SettingOption{}; + options.wifi_network_option_labels[i][0] = '\0'; + options.wifi_scan_results[i] = wifi_runtime::ScanResult{}; } g_settings.wifi_network_index = -1; } @@ -315,19 +352,20 @@ static void clear_wifi_scan_options() static void rebuild_wifi_scan_options(const std::vector& results) { clear_wifi_scan_options(); + auto& options = dynamic_options(); const size_t limit = results.size() < kMaxWifiNetworks ? results.size() : kMaxWifiNetworks; for (size_t i = 0; i < limit; ++i) { - kWifiScanResults[i] = results[i]; - std::snprintf(kWifiNetworkOptionLabels[i], - sizeof(kWifiNetworkOptionLabels[i]), + options.wifi_scan_results[i] = results[i]; + std::snprintf(options.wifi_network_option_labels[i], + sizeof(options.wifi_network_option_labels[i]), "%s (%d dBm%s)", results[i].ssid, results[i].rssi, results[i].requires_password ? ", lock" : ""); - kWifiNetworkOptions[i].label = kWifiNetworkOptionLabels[i]; - kWifiNetworkOptions[i].value = static_cast(i); + options.wifi_network_options[i].label = options.wifi_network_option_labels[i]; + options.wifi_network_options[i].value = static_cast(i); } kWifiNetworkOptionCount = limit; } @@ -1230,6 +1268,8 @@ static void settings_load() #if defined(ESP_PLATFORM) ESP_LOGI(kLogTag, "settings_load begin"); #endif + bind_dynamic_option_storage_to_items(); + auto& options = dynamic_options(); app::IAppFacade& app_ctx = app::appFacade(); g_settings.chat_protocol = static_cast(app_ctx.getConfig().mesh_protocol); @@ -1237,12 +1277,12 @@ static void settings_load() { size_t region_count = 0; const chat::meshtastic::RegionInfo* regions = chat::meshtastic::getRegionTable(®ion_count); - size_t limit = sizeof(kChatRegionOptions) / sizeof(kChatRegionOptions[0]); + size_t limit = kChatRegionOptionCapacity; kChatRegionOptionCount = (region_count < limit) ? region_count : limit; for (size_t i = 0; i < kChatRegionOptionCount; ++i) { - kChatRegionOptions[i].label = regions[i].label; - kChatRegionOptions[i].value = regions[i].code; + options.chat_region_options[i].label = regions[i].label; + options.chat_region_options[i].value = regions[i].code; } } if (kMeshCoreRegionPresetOptionCount == 0) @@ -1250,11 +1290,11 @@ static void settings_load() size_t preset_count = 0; const chat::meshcore::RegionPreset* presets = chat::meshcore::getRegionPresetTable(&preset_count); - size_t limit = sizeof(kMeshCoreRegionPresetOptions) / sizeof(kMeshCoreRegionPresetOptions[0]); + size_t limit = kMeshCoreRegionPresetOptionCapacity; if (limit > 0) { - kMeshCoreRegionPresetOptions[0].label = "Custom"; - kMeshCoreRegionPresetOptions[0].value = 0; + options.meshcore_region_preset_options[0].label = "Custom"; + options.meshcore_region_preset_options[0].value = 0; size_t copy_count = preset_count; if (copy_count > (limit - 1)) { @@ -1262,24 +1302,25 @@ static void settings_load() } for (size_t i = 0; i < copy_count; ++i) { - kMeshCoreRegionPresetOptions[i + 1].label = presets[i].title; - kMeshCoreRegionPresetOptions[i + 1].value = presets[i].id; + options.meshcore_region_preset_options[i + 1].label = presets[i].title; + options.meshcore_region_preset_options[i + 1].value = presets[i].id; } kMeshCoreRegionPresetOptionCount = copy_count + 1; } } if (kTxPowerOptionCount == 0) { - size_t limit = sizeof(kTxPowerOptions) / sizeof(kTxPowerOptions[0]); + size_t limit = kTxPowerOptionCapacity; int value = kNetTxPowerMin; while (value <= kNetTxPowerMax && kTxPowerOptionCount < limit) { - snprintf(kTxPowerLabels[kTxPowerOptionCount], - sizeof(kTxPowerLabels[kTxPowerOptionCount]), + snprintf(options.tx_power_labels[kTxPowerOptionCount], + sizeof(options.tx_power_labels[kTxPowerOptionCount]), "%d dBm", value); - kTxPowerOptions[kTxPowerOptionCount].label = kTxPowerLabels[kTxPowerOptionCount]; - kTxPowerOptions[kTxPowerOptionCount].value = value; + options.tx_power_options[kTxPowerOptionCount].label = + options.tx_power_labels[kTxPowerOptionCount]; + options.tx_power_options[kTxPowerOptionCount].value = value; kTxPowerOptionCount++; value++; } @@ -1491,8 +1532,9 @@ static void settings_load() static void refresh_language_pack_options() { + auto& options = dynamic_options(); kLocaleOptionCount = 0; - const size_t locale_limit = sizeof(kLocaleOptions) / sizeof(kLocaleOptions[0]); + const size_t locale_limit = kLocaleOptionCapacity; for (size_t index = 0; index < ::ui::i18n::locale_count() && kLocaleOptionCount < locale_limit; ++index) { const ::ui::i18n::LocaleInfo* locale = ::ui::i18n::locale_at(index); @@ -1503,12 +1545,13 @@ static void refresh_language_pack_options() const char* display_name = (locale->native_name && locale->native_name[0] != '\0') ? locale->native_name : locale->display_name; - std::snprintf(kLocaleOptionLabels[kLocaleOptionCount], - sizeof(kLocaleOptionLabels[kLocaleOptionCount]), + std::snprintf(options.locale_option_labels[kLocaleOptionCount], + sizeof(options.locale_option_labels[kLocaleOptionCount]), "%s", display_name ? display_name : ""); - kLocaleOptions[kLocaleOptionCount].label = kLocaleOptionLabels[kLocaleOptionCount]; - kLocaleOptions[kLocaleOptionCount].value = static_cast(index); + options.locale_options[kLocaleOptionCount].label = + options.locale_option_labels[kLocaleOptionCount]; + options.locale_options[kLocaleOptionCount].value = static_cast(index); ++kLocaleOptionCount; } g_settings.display_locale_index = ::ui::i18n::current_locale_index(); @@ -2363,7 +2406,7 @@ static void on_option_clicked(lv_event_t* e) static_cast(payload->value) < kWifiNetworkOptionCount) { const wifi_runtime::ScanResult& result = - kWifiScanResults[static_cast(payload->value)]; + dynamic_options().wifi_scan_results[static_cast(payload->value)]; copy_bounded(g_settings.wifi_ssid, sizeof(g_settings.wifi_ssid), result.ssid); wifi_runtime::Config config{}; config.enabled = g_settings.wifi_enabled; @@ -3677,7 +3720,7 @@ static settings::ui::SettingItem kChatItems[] = { {"User Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.user_name, sizeof(g_settings.user_name), false, "chat_user"}, {"Short Name", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.short_name, sizeof(g_settings.short_name), false, "chat_short"}, {"Protocol", settings::ui::SettingType::Enum, kChatProtocolOptions, 4, &g_settings.chat_protocol, nullptr, nullptr, 0, false, "mesh_protocol"}, - {"Region", settings::ui::SettingType::Enum, kChatRegionOptions, 0, &g_settings.chat_region, nullptr, nullptr, 0, false, "chat_region"}, + {"Region", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.chat_region, nullptr, nullptr, 0, false, "chat_region"}, {"Channel", settings::ui::SettingType::Enum, kChatChannelOptions, 2, &g_settings.chat_channel, nullptr, nullptr, 0, false, "chat_channel"}, {"Channel Key / PSK", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.chat_psk, sizeof(g_settings.chat_psk), true, "chat_psk"}, {"Encryption Mode", settings::ui::SettingType::Enum, kPrivacyEncryptOptions, 3, &g_settings.privacy_encrypt_mode, nullptr, nullptr, 0, false, "privacy_encrypt"}, @@ -3694,7 +3737,7 @@ static settings::ui::SettingItem kNetworkItems[] = { {"Manual BW", settings::ui::SettingType::Enum, kNetManualBwOptions, sizeof(kNetManualBwOptions) / sizeof(kNetManualBwOptions[0]), &g_settings.net_manual_bw, nullptr, nullptr, 0, false, "net_bw"}, {"Manual SF", settings::ui::SettingType::Enum, kSfOptions, sizeof(kSfOptions) / sizeof(kSfOptions[0]), &g_settings.net_manual_sf, nullptr, nullptr, 0, false, "net_sf"}, {"Manual CR", settings::ui::SettingType::Enum, kCrOptions, sizeof(kCrOptions) / sizeof(kCrOptions[0]), &g_settings.net_manual_cr, nullptr, nullptr, 0, false, "net_cr"}, - {"TX Power", settings::ui::SettingType::Enum, kTxPowerOptions, + {"TX Power", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.net_tx_power, nullptr, nullptr, 0, false, "net_tx_power"}, {"Hop Limit", settings::ui::SettingType::Enum, kHopLimitOptions, sizeof(kHopLimitOptions) / sizeof(kHopLimitOptions[0]), &g_settings.net_hop_limit, nullptr, nullptr, 0, false, "net_hop_limit"}, {"TX Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.net_tx_enabled, nullptr, 0, false, "net_tx_enabled"}, @@ -3702,12 +3745,12 @@ static settings::ui::SettingItem kNetworkItems[] = { {"Channel Slot", settings::ui::SettingType::Enum, kChannelNumOptions, sizeof(kChannelNumOptions) / sizeof(kChannelNumOptions[0]), &g_settings.net_channel_num, nullptr, nullptr, 0, false, "net_channel_num"}, {"Freq Offset (MHz)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.net_freq_offset, sizeof(g_settings.net_freq_offset), false, "net_freq_offset"}, {"Override Freq (MHz)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.net_override_freq, sizeof(g_settings.net_override_freq), false, "net_override_freq"}, - {"MC Region Preset", settings::ui::SettingType::Enum, kMeshCoreRegionPresetOptions, 0, &g_settings.mc_region_preset, nullptr, nullptr, 0, false, "mc_region_preset"}, + {"MC Region Preset", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.mc_region_preset, nullptr, nullptr, 0, false, "mc_region_preset"}, {"MC Frequency (MHz)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_freq, sizeof(g_settings.mc_freq), false, "mc_freq"}, {"MC Bandwidth (kHz)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_bw, sizeof(g_settings.mc_bw), false, "mc_bw"}, {"MC Spread Factor", settings::ui::SettingType::Enum, kSfOptions, sizeof(kSfOptions) / sizeof(kSfOptions[0]), &g_settings.mc_sf, nullptr, nullptr, 0, false, "mc_sf"}, {"MC Coding Rate", settings::ui::SettingType::Enum, kCrOptions, sizeof(kCrOptions) / sizeof(kCrOptions[0]), &g_settings.mc_cr, nullptr, nullptr, 0, false, "mc_cr"}, - {"MC TX Power", settings::ui::SettingType::Enum, kTxPowerOptions, 0, &g_settings.mc_tx_power, nullptr, nullptr, 0, false, "mc_tx_power"}, + {"MC TX Power", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.mc_tx_power, nullptr, nullptr, 0, false, "mc_tx_power"}, {"MC Repeat", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.mc_client_repeat, nullptr, 0, false, "mc_repeat"}, {"MC RX Delay Base", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_rx_delay, sizeof(g_settings.mc_rx_delay), false, "mc_rx_delay"}, {"MC Airtime Factor", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.mc_airtime, sizeof(g_settings.mc_airtime), false, "mc_airtime"}, @@ -3723,7 +3766,7 @@ static settings::ui::SettingItem kNetworkItems[] = { }; static settings::ui::SettingItem kScreenItems[] = { - {"Display Language", settings::ui::SettingType::Enum, kLocaleOptions, + {"Display Language", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.display_locale_index, nullptr, nullptr, 0, false, "display_locale"}, {"Enabled IMEs", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "enabled_imes"}, @@ -3737,7 +3780,7 @@ static settings::ui::SettingItem kScreenItems[] = { {"C6 Companion", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.c6_companion_status, sizeof(g_settings.c6_companion_status), false, "c6_companion_status"}, {"C6 Download Mode", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "c6_enter_download"}, - {"Time Zone", settings::ui::SettingType::Enum, kTimeZoneOptions, 0, &g_settings.timezone_profile_id, nullptr, nullptr, 0, false, "timezone_profile"}, + {"Time Zone", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.timezone_profile_id, nullptr, nullptr, 0, false, "timezone_profile"}, {"Date/Time", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "manual_time_set"}, {"Gauge Design (mAh)", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.gauge_design_mah, sizeof(g_settings.gauge_design_mah), false, "gauge_design_mah"}, @@ -3750,7 +3793,7 @@ static settings::ui::SettingItem kWifiItems[] = { {"Wi-Fi Enabled", settings::ui::SettingType::Toggle, nullptr, 0, nullptr, &g_settings.wifi_enabled, nullptr, 0, false, "wifi_enabled"}, {"Status", settings::ui::SettingType::Info, nullptr, 0, nullptr, nullptr, g_settings.wifi_status, sizeof(g_settings.wifi_status), false, "wifi_status"}, {"Scan Networks", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "wifi_scan"}, - {"Detected Network", settings::ui::SettingType::Enum, kWifiNetworkOptions, 0, &g_settings.wifi_network_index, nullptr, nullptr, 0, false, "wifi_network"}, + {"Detected Network", settings::ui::SettingType::Enum, nullptr, 0, &g_settings.wifi_network_index, nullptr, nullptr, 0, false, "wifi_network"}, {"SSID", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.wifi_ssid, sizeof(g_settings.wifi_ssid), false, "wifi_ssid"}, {"Password", settings::ui::SettingType::Text, nullptr, 0, nullptr, nullptr, g_settings.wifi_password, sizeof(g_settings.wifi_password), true, "wifi_password"}, {"Connect", settings::ui::SettingType::Action, nullptr, 0, nullptr, nullptr, nullptr, 0, false, "wifi_connect"}, @@ -4400,7 +4443,8 @@ static bool activate_item_widget(settings::ui::ItemWidget& widget) rebuild_wifi_scan_options(results); for (size_t i = 0; i < kWifiNetworkOptionCount; ++i) { - if (std::strcmp(kWifiScanResults[i].ssid, g_settings.wifi_ssid) == 0) + if (std::strcmp(dynamic_options().wifi_scan_results[i].ssid, + g_settings.wifi_ssid) == 0) { g_settings.wifi_network_index = static_cast(i); break; @@ -4590,6 +4634,47 @@ static void settings_back_cb(void* /*user_data*/) ui_request_exit_to_menu(); } +static void bind_dynamic_option_storage_to_items() +{ + auto& options = dynamic_options(); + for (settings::ui::SettingItem& item : kChatItems) + { + if (has_pref_key(item, "chat_region")) + { + item.options = options.chat_region_options; + } + } + for (settings::ui::SettingItem& item : kNetworkItems) + { + if (has_pref_key(item, "net_tx_power") || has_pref_key(item, "mc_tx_power")) + { + item.options = options.tx_power_options; + } + else if (has_pref_key(item, "mc_region_preset")) + { + item.options = options.meshcore_region_preset_options; + } + } + for (settings::ui::SettingItem& item : kScreenItems) + { + if (has_pref_key(item, "display_locale")) + { + item.options = options.locale_options; + } + else if (has_pref_key(item, "timezone_profile")) + { + item.options = options.time_zone_options; + } + } + for (settings::ui::SettingItem& item : kWifiItems) + { + if (has_pref_key(item, "wifi_network")) + { + item.options = options.wifi_network_options; + } + } +} + static void refresh_timezone_option_count() { for (settings::ui::SettingItem& item : kScreenItems) diff --git a/modules/ui_shared/src/ui/screens/settings/settings_state.cpp b/modules/ui_shared/src/ui/screens/settings/settings_state.cpp index 37017a9a..77fc60d9 100644 --- a/modules/ui_shared/src/ui/screens/settings/settings_state.cpp +++ b/modules/ui_shared/src/ui/screens/settings/settings_state.cpp @@ -5,10 +5,17 @@ #include "ui/screens/settings/settings_state.h" +#if defined(ESP_PLATFORM) +#include "esp_attr.h" +#define UI_SETTINGS_STATE_RAM_ATTR EXT_RAM_ATTR +#else +#define UI_SETTINGS_STATE_RAM_ATTR +#endif + namespace settings::ui { -SettingsData g_settings{}; -UiState g_state{}; +UI_SETTINGS_STATE_RAM_ATTR SettingsData g_settings{}; +UI_SETTINGS_STATE_RAM_ATTR UiState g_state{}; } // namespace settings::ui diff --git a/modules/ui_shared/src/ui/startup_shell.cpp b/modules/ui_shared/src/ui/startup_shell.cpp index a0763c80..57eee955 100644 --- a/modules/ui_shared/src/ui/startup_shell.cpp +++ b/modules/ui_shared/src/ui/startup_shell.cpp @@ -26,8 +26,18 @@ namespace ui::menu::MenuModel s_ux_menu_model; +#ifndef TRAIL_MATE_BOOT_UI_SYNC_PRESENT +#if defined(ARDUINO) +#define TRAIL_MATE_BOOT_UI_SYNC_PRESENT 0 +#else +#define TRAIL_MATE_BOOT_UI_SYNC_PRESENT 1 +#endif +#endif + +#if TRAIL_MATE_BOOT_UI_SYNC_PRESENT constexpr uint8_t kBootPresentFrameCount = 4; constexpr uint32_t kBootPresentFrameDelayMs = 16; +#endif bool resolve_display_time(struct tm* out_tm) { @@ -60,6 +70,7 @@ bool resolve_display_time(struct tm* out_tm) void present_boot_overlay_now() { +#if TRAIL_MATE_BOOT_UI_SYNC_PRESENT for (uint8_t frame = 0; frame < kBootPresentFrameCount; ++frame) { if (lv_obj_t* top = lv_layer_top()) @@ -73,6 +84,12 @@ void present_boot_overlay_now() sys::sleep_ms(kBootPresentFrameDelayMs); } } +#else + if (lv_obj_t* top = lv_layer_top()) + { + lv_obj_invalidate(top); + } +#endif } } // namespace diff --git a/modules/ui_shared/src/ui/startup_ui_shell.cpp b/modules/ui_shared/src/ui/startup_ui_shell.cpp index 963a325d..1da88b2a 100644 --- a/modules/ui_shared/src/ui/startup_ui_shell.cpp +++ b/modules/ui_shared/src/ui/startup_ui_shell.cpp @@ -23,11 +23,22 @@ namespace bool s_shell_initialized = false; ui::menu::MenuModel s_ux_menu_model; +#ifndef TRAIL_MATE_BOOT_UI_SYNC_PRESENT +#if defined(ARDUINO) +#define TRAIL_MATE_BOOT_UI_SYNC_PRESENT 0 +#else +#define TRAIL_MATE_BOOT_UI_SYNC_PRESENT 1 +#endif +#endif + +#if TRAIL_MATE_BOOT_UI_SYNC_PRESENT constexpr uint8_t kBootPresentFrameCount = 4; constexpr uint32_t kBootPresentFrameDelayMs = 16; +#endif void present_boot_overlay_now() { +#if TRAIL_MATE_BOOT_UI_SYNC_PRESENT for (uint8_t frame = 0; frame < kBootPresentFrameCount; ++frame) { if (lv_obj_t* top = lv_layer_top()) @@ -41,6 +52,12 @@ void present_boot_overlay_now() sys::sleep_ms(kBootPresentFrameDelayMs); } } +#else + if (lv_obj_t* top = lv_layer_top()) + { + lv_obj_invalidate(top); + } +#endif } bool resolve_display_time(struct tm* out_tm) diff --git a/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp b/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp index 2ab9eee8..8351d935 100644 --- a/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp +++ b/modules/ui_shared/src/ui/widgets/map/map_viewport.cpp @@ -14,14 +14,40 @@ #include #include #include +#include #include #include +#if defined(ESP_PLATFORM) +#include "esp_heap_caps.h" +#endif + namespace ui::widgets::map { struct RuntimeImpl { +#if defined(ESP_PLATFORM) + void* operator new(std::size_t size) + { + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); + } + + void operator delete(void* ptr) noexcept + { + heap_caps_free(ptr); + } + + void operator delete(void* ptr, std::size_t) noexcept + { + operator delete(ptr); + } +#endif + Widgets widgets{}; Model model{}; ui::map::MapOverlaySnapshot overlay{}; diff --git a/platform/esp/arduino_common/include/ble/meshcore_ble.h b/platform/esp/arduino_common/include/ble/meshcore_ble.h index d667c3e6..c66d02ed 100644 --- a/platform/esp/arduino_common/include/ble/meshcore_ble.h +++ b/platform/esp/arduino_common/include/ble/meshcore_ble.h @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +30,10 @@ class MeshCoreBleService : public BleService, MeshCoreBleService(app::IAppBleFacade& ctx, const std::string& device_name); ~MeshCoreBleService() override; + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + bool start() override; void stop() override; void update() override; @@ -134,7 +139,7 @@ class MeshCoreBleService : public BleService, std::unique_ptr shared_core_; void setupService(); - void startAdvertising(); + bool startAdvertising(); void handleIncomingFrames(); void handleCmdFrame(size_t len); void enqueueFrame(const uint8_t* data, size_t len); diff --git a/platform/esp/arduino_common/include/ble/meshtastic_ble.h b/platform/esp/arduino_common/include/ble/meshtastic_ble.h index aed2e390..04160942 100644 --- a/platform/esp/arduino_common/include/ble/meshtastic_ble.h +++ b/platform/esp/arduino_common/include/ble/meshtastic_ble.h @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,10 @@ class MeshtasticBleService : public BleService, MeshtasticBleService(app::IAppBleFacade& ctx, const std::string& device_name); ~MeshtasticBleService() override; + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + bool start() override; void stop() override; void update() override; @@ -118,7 +123,7 @@ class MeshtasticBleService : public BleService, } void setupService(); - void startAdvertising(); + bool startAdvertising(); void requestHighThroughputConnection(); void requestLowerPowerConnection(); void handleFromPhone(); diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h index ef7137c2..ab2913fd 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/app_tasks.h @@ -21,8 +21,9 @@ namespace app class AppTasks { public: - static constexpr size_t RADIO_QUEUE_SIZE = 10; - static constexpr size_t MESH_QUEUE_SIZE = 10; + // Keep always-on radio queues shallow so BLE/NimBLE retains internal RAM. + static constexpr size_t RADIO_QUEUE_SIZE = 6; + static constexpr size_t MESH_QUEUE_SIZE = 6; struct RadioPacket { diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/contact_store.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/contact_store.h index 63560815..3b04043d 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/contact_store.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/contact_store.h @@ -8,6 +8,8 @@ #include "chat/infra/contact_store_core.h" #include "chat/ports/i_contact_blob_store.h" +#include + namespace chat { namespace contacts @@ -17,6 +19,10 @@ class ContactStore : public IContactStore, private chat::IContactBlobStore { public: + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t) noexcept; + ContactStore(); void begin() override; diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h index 953eb32f..0244ed42 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/lxmf/lxmf_adapter.h @@ -13,6 +13,7 @@ #include "platform/esp/arduino_common/chat/infra/rnode/rnode_adapter.h" #include +#include #include #include @@ -24,6 +25,10 @@ class LxmfAdapter : public IMeshAdapter public: explicit LxmfAdapter(LoraBoard& board); + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + MeshCapabilities getCapabilities() const override; bool sendText(ChannelId channel, const std::string& text, MessageId* out_msg_id, NodeId peer = 0) override; diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h index 8e3ff1ed..fbf337d1 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshcore/meshcore_adapter.h @@ -12,6 +12,7 @@ #include "chat/runtime/meshcore_runtime.h" #include "chat/runtime/protocol_runtime_factory.h" #include "platform/esp/arduino_common/chat/infra/meshcore/meshcore_identity.h" +#include #include #include #include @@ -40,6 +41,11 @@ class MeshCoreAdapter : public IMeshAdapter, * @brief Destructor */ ~MeshCoreAdapter() override = default; + + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + MeshCapabilities getCapabilities() const override; // IMeshAdapter interface implementation diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/mt_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/mt_adapter.h index fd448acc..ab0a4c95 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/mt_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/mt_adapter.h @@ -16,6 +16,7 @@ #include "freertos/queue.h" #include "platform/esp/arduino_common/mesh/esp_meshtastic_adapter_bridge.h" #include +#include #include #include #include @@ -49,6 +50,11 @@ class MtAdapter : public chat::IMeshAdapter MtAdapter(LoraBoard& board); virtual ~MtAdapter(); + + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + MeshCapabilities getCapabilities() const override; bool sendText(ChannelId channel, const std::string& text, diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/node_store.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/node_store.h index fb9ea529..404819a9 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/node_store.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/meshtastic/node_store.h @@ -13,6 +13,7 @@ #include "freertos/semphr.h" #include "freertos/task.h" +#include #include namespace chat @@ -24,6 +25,10 @@ class NodeStore : public contacts::INodeStore, private contacts::INodeBlobStore { public: + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t) noexcept; + NodeStore(); void begin() override; diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/rnode/rnode_adapter.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/rnode/rnode_adapter.h index c091275a..f27cd161 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/rnode/rnode_adapter.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/chat/infra/rnode/rnode_adapter.h @@ -8,6 +8,7 @@ #include "board/LoraBoard.h" #include "chat/infra/rnode/rnode_packet_wire.h" #include "chat/ports/i_mesh_adapter.h" +#include #include namespace chat @@ -20,6 +21,10 @@ class RNodeAdapter : public IMeshAdapter public: explicit RNodeAdapter(LoraBoard& board); + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + MeshCapabilities getCapabilities() const override; bool sendText(ChannelId channel, const std::string& text, MessageId* out_msg_id, NodeId peer = 0) override; diff --git a/platform/esp/arduino_common/include/platform/esp/arduino_common/mesh/esp_meshtastic_adapter_bridge.h b/platform/esp/arduino_common/include/platform/esp/arduino_common/mesh/esp_meshtastic_adapter_bridge.h index c082036f..282a1c42 100644 --- a/platform/esp/arduino_common/include/platform/esp/arduino_common/mesh/esp_meshtastic_adapter_bridge.h +++ b/platform/esp/arduino_common/include/platform/esp/arduino_common/mesh/esp_meshtastic_adapter_bridge.h @@ -13,6 +13,8 @@ #include "mesh/usecase/receive_packet_service.h" #include "platform/esp/arduino_common/mesh/esp_preferences_mesh_identity_store.h" +#include + namespace platform::esp::arduino_common::mesh { @@ -74,6 +76,10 @@ class EspMeshtasticAdapterBridge final public: explicit EspMeshtasticAdapterBridge(LoraBoard& board); + static void* operator new(std::size_t size); + static void operator delete(void* ptr) noexcept; + static void operator delete(void* ptr, std::size_t size) noexcept; + ::mesh::SendResult sendDirect(const ::mesh::DirectMessageCommand& command); bool copyLastSentPacket(uint8_t* out, size_t capacity, size_t& out_size) const; void onRadioPacket(const uint8_t* data, size_t size, int16_t rssi, int8_t snr); diff --git a/platform/esp/arduino_common/src/LV_Helper_v9.cpp b/platform/esp/arduino_common/src/LV_Helper_v9.cpp index b598d338..825c1678 100644 --- a/platform/esp/arduino_common/src/LV_Helper_v9.cpp +++ b/platform/esp/arduino_common/src/LV_Helper_v9.cpp @@ -47,6 +47,13 @@ namespace { constexpr char kLvglFlashFsLetter = 'F'; constexpr const char* kLvglFlashFsMountPoint = "/fs"; +#if defined(ARDUINO_T_DECK) +#ifndef TRAIL_MATE_TDECK_DMA_DRAW_BUFFER_LINES +#define TRAIL_MATE_TDECK_DMA_DRAW_BUFFER_LINES 40 +#endif +constexpr size_t kTDeckDmaDrawBufferLines = TRAIL_MATE_TDECK_DMA_DRAW_BUFFER_LINES; +static_assert(kTDeckDmaDrawBufferLines >= 10, "TDeck draw buffers below 10 lines make refresh visibly unstable"); +#endif // LVGL's SD drive callback can still be reached by resource-pack and image // paths. Keep every callback acquisition extremely short: a busy shared // bus must surface as LV_FS_RES_BUSY/failed open, not as a blocked UI frame. @@ -1017,8 +1024,12 @@ void beginLvglHelper(LilyGo_Display& board, bool debug) // For DMA, keep internal RAM pressure low to avoid starving other subsystems. #if LV_TEST_FORCE_DMA_FULL_SIZE // keep full size for testing if memory allows +#else +#if defined(ARDUINO_T_DECK) + lv_buffer_size = board.width() * kTDeckDmaDrawBufferLines * sizeof(lv_color16_t); #else lv_buffer_size = (board.width() * board.height() / 6) * sizeof(lv_color16_t); +#endif #endif buf = (lv_color16_t*)heap_caps_malloc(lv_buffer_size, MALLOC_CAP_DMA); buf1 = (lv_color16_t*)heap_caps_malloc(lv_buffer_size, MALLOC_CAP_DMA); diff --git a/platform/esp/arduino_common/src/app_context.cpp b/platform/esp/arduino_common/src/app_context.cpp index f41e3318..60e24524 100644 --- a/platform/esp/arduino_common/src/app_context.cpp +++ b/platform/esp/arduino_common/src/app_context.cpp @@ -21,12 +21,14 @@ #include #include +#include +#include namespace app { namespace { -constexpr uint32_t kConfigSaveTaskStackBytes = 8 * 1024; +constexpr uint32_t kConfigSaveTaskStackBytes = 4 * 1024; constexpr UBaseType_t kConfigSaveTaskPriority = 1; constexpr TickType_t kConfigSaveMutexWait = pdMS_TO_TICKS(20); constexpr TickType_t kConfigSaveDebounceTicks = pdMS_TO_TICKS(250); @@ -35,8 +37,19 @@ constexpr TickType_t kConfigSaveRetryDelayTicks = pdMS_TO_TICKS(1000); AppContext& AppContext::getInstance() { - static AppContext instance; - return instance; + static AppContext* instance = []() -> AppContext* + { + void* storage = heap_caps_malloc_prefer(sizeof(AppContext), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(AppContext)); + } + return new (storage) AppContext(); + }(); + return *instance; } AppContext::AppContext() diff --git a/platform/esp/arduino_common/src/app_context_platform_bindings.cpp b/platform/esp/arduino_common/src/app_context_platform_bindings.cpp index c9a3c421..c32f0df0 100644 --- a/platform/esp/arduino_common/src/app_context_platform_bindings.cpp +++ b/platform/esp/arduino_common/src/app_context_platform_bindings.cpp @@ -150,15 +150,9 @@ app::ContactServicesBundle create_contact_services() if (bundle.service) { bundle.service->begin(); - const std::vector contacts = bundle.service->getContacts(); - const std::vector nearby = bundle.service->getNearby(); - const std::vector ignored = bundle.service->getIgnoredNodes(); - Serial.printf("[ContactService] startup nodes=%u nicknames=%u contacts=%u nearby=%u ignored=%u\n", + Serial.printf("[ContactService] startup nodes=%u nicknames=%u\n", static_cast(bundle.node_store->getEntries().size()), - static_cast(bundle.contact_store->getCount()), - static_cast(contacts.size()), - static_cast(nearby.size()), - static_cast(ignored.size())); + static_cast(bundle.contact_store->getCount())); } return bundle; } diff --git a/platform/esp/arduino_common/src/app_tasks.cpp b/platform/esp/arduino_common/src/app_tasks.cpp index 73ca2078..0e62e26b 100644 --- a/platform/esp/arduino_common/src/app_tasks.cpp +++ b/platform/esp/arduino_common/src/app_tasks.cpp @@ -216,7 +216,7 @@ bool AppTasks::init(LoraBoard& board, chat::IMeshAdapter* adapter) BaseType_t result = xTaskCreate( radioTask, "radio_task", - 4 * 1024, // Stack size + 3 * 1024, // Stack size nullptr, 10, // High priority &radio_task_handle_); @@ -230,7 +230,7 @@ bool AppTasks::init(LoraBoard& board, chat::IMeshAdapter* adapter) result = xTaskCreate( meshTask, "mesh_task", - 6 * 1024, // Stack size + 4 * 1024, // Stack size nullptr, 5, // Medium priority &mesh_task_handle_); diff --git a/platform/esp/arduino_common/src/ble/meshcore_ble.cpp b/platform/esp/arduino_common/src/ble/meshcore_ble.cpp index f692c6fb..b7132b2d 100644 --- a/platform/esp/arduino_common/src/ble/meshcore_ble.cpp +++ b/platform/esp/arduino_common/src/ble/meshcore_ble.cpp @@ -11,7 +11,9 @@ #include #include #include +#include #include +#include #include #include @@ -460,9 +462,10 @@ class MeshCoreServerCallbacks : public NimBLEServerCallbacks owner_.pending_passkey_.store(0); owner_.outbound_.clear(); owner_.rx_queue_.clear(); - owner_.startAdvertising(); - Serial.printf("[BLE][meshcore] disconnected reason=%d; advertising restarted uuid=%s\n", + const bool advertising_ok = owner_.startAdvertising(); + Serial.printf("[BLE][meshcore] disconnected reason=%d advertising_restart=%u uuid=%s\n", reason, + advertising_ok ? 1U : 0U, NUS_SERVICE_UUID); } @@ -485,6 +488,25 @@ MeshCoreBleService::~MeshCoreBleService() stop(); } +void* MeshCoreBleService::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MeshCoreBleService::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MeshCoreBleService::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + uint32_t MeshCoreBleService::effectiveBlePin() const { return active_ble_pin_; @@ -883,7 +905,12 @@ bool MeshCoreBleService::start() stop(); return false; } - startAdvertising(); + if (!startAdvertising()) + { + Serial.printf("[BLE][meshcore] start failed reason=advertising\n"); + stop(); + return false; + } multi_acks_ = phone_facade_.getMeshCorePhoneConfig().mesh.meshcore_multi_acks ? 1 : 0; @@ -1410,11 +1437,11 @@ void MeshCoreBleService::setupService() service_->start(); } -void MeshCoreBleService::startAdvertising() +bool MeshCoreBleService::startAdvertising() { if (!server_) { - return; + return false; } NimBLEAdvertising* adv = server_->getAdvertising(); const bool reset_ok = adv->reset(); @@ -1435,6 +1462,8 @@ void MeshCoreBleService::startAdvertising() name_ok ? 1U : 0U, start_ok ? 1U : 0U, active_ok ? 1U : 0U); + return reset_ok && conn_ok && disc_ok && service_ok && + start_ok && active_ok; } void MeshCoreBleService::handleIncomingFrames() diff --git a/platform/esp/arduino_common/src/ble/meshtastic_ble.cpp b/platform/esp/arduino_common/src/ble/meshtastic_ble.cpp index 14e309d1..b9668a39 100644 --- a/platform/esp/arduino_common/src/ble/meshtastic_ble.cpp +++ b/platform/esp/arduino_common/src/ble/meshtastic_ble.cpp @@ -22,7 +22,9 @@ #include #include #include +#include #include +#include #include #if defined(TRAIL_MATE_ESP_BOARD_TAB5) @@ -47,7 +49,11 @@ inline void ble_log(const char* fmt, ...) } constexpr size_t kMaxFromRadio = meshtastic_FromRadio_size; constexpr size_t kMaxToRadio = meshtastic_ToRadio_size; +#if defined(CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU) +constexpr uint16_t kPreferredBleMtu = CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU; +#else constexpr uint16_t kPreferredBleMtu = 517; +#endif constexpr bool kEnableFromRadioSync = false; const char* pairingModeName(meshtastic_Config_BluetoothConfig_PairingMode mode) @@ -256,8 +262,11 @@ class MeshtasticServerCallbacks : public NimBLEServerCallbacks memset(owner_.last_to_radio_.data(), 0, owner_.last_to_radio_.size()); owner_.closePhoneSession(); owner_.clearQueues(); - owner_.startAdvertising(); - ble_log("disconnected reason=%d; advertising restarted uuid=%s", reason, MESH_SERVICE_UUID); + const bool advertising_ok = owner_.startAdvertising(); + ble_log("disconnected reason=%d advertising_restart=%u uuid=%s", + reason, + advertising_ok ? 1U : 0U, + MESH_SERVICE_UUID); } private: @@ -419,6 +428,25 @@ MeshtasticBleService::~MeshtasticBleService() stop(); } +void* MeshtasticBleService::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MeshtasticBleService::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MeshtasticBleService::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + bool MeshtasticBleService::start() { loadBleConfig(); @@ -467,7 +495,12 @@ bool MeshtasticBleService::start() stop(); return false; } - startAdvertising(); + if (!startAdvertising()) + { + ble_log("start failed reason=advertising"); + stop(); + return false; + } if (observer_bridge_) { @@ -667,11 +700,11 @@ void MeshtasticBleService::setupService() refreshBatteryLevel(false); } -void MeshtasticBleService::startAdvertising() +bool MeshtasticBleService::startAdvertising() { if (!server_) { - return; + return false; } NimBLEAdvertising* adv = server_->getAdvertising(); const bool reset_ok = adv->reset(); @@ -684,9 +717,11 @@ void MeshtasticBleService::startAdvertising() const bool scan_data_ok = adv->setScanResponseData(scan_data); const bool preferred_ok = adv->setPreferredParams(0x06, 0x12); const bool start_ok = adv->start(); - ble_log("advertising uuid=%s name=%s reset_ok=%u conn_ok=%u disc_ok=%u mesh_ok=%u scan_name_ok=%u scan_ok=%u pref_ok=%u start_ok=%u", + const bool active_ok = adv->isAdvertising(); + ble_log("advertising uuid=%s name=%s", MESH_SERVICE_UUID, - device_name_.c_str(), + device_name_.c_str()); + ble_log("advertising result reset=%u conn=%u disc=%u mesh=%u scan_name=%u scan=%u pref=%u start=%u active=%u", reset_ok ? 1U : 0U, conn_ok ? 1U : 0U, disc_ok ? 1U : 0U, @@ -694,7 +729,10 @@ void MeshtasticBleService::startAdvertising() scan_name_ok ? 1U : 0U, scan_data_ok ? 1U : 0U, preferred_ok ? 1U : 0U, - start_ok ? 1U : 0U); + start_ok ? 1U : 0U, + active_ok ? 1U : 0U); + return reset_ok && conn_ok && disc_ok && mesh_service_ok && + scan_data_ok && start_ok && active_ok; } void MeshtasticBleService::requestHighThroughputConnection() diff --git a/platform/esp/arduino_common/src/chat/infra/contact_store.cpp b/platform/esp/arduino_common/src/chat/infra/contact_store.cpp index d1a4311f..1e2e587f 100644 --- a/platform/esp/arduino_common/src/chat/infra/contact_store.cpp +++ b/platform/esp/arduino_common/src/chat/infra/contact_store.cpp @@ -9,6 +9,8 @@ #include "platform/esp/common/shared_spi_lock.h" #include +#include +#include namespace chat { @@ -25,6 +27,25 @@ namespace contacts #define CONTACT_STORE_LOG(...) #endif +void* ContactStore::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void ContactStore::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void ContactStore::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + namespace { constexpr TickType_t kSdLoadWait = pdMS_TO_TICKS(250); diff --git a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp index 23d6829d..91812bea 100644 --- a/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/lxmf/lxmf_adapter.cpp @@ -13,10 +13,12 @@ #include #include +#include #include #include #include +#include namespace chat::lxmf { @@ -545,6 +547,25 @@ LxmfAdapter::LxmfAdapter(LoraBoard& board) } } +void* LxmfAdapter::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void LxmfAdapter::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void LxmfAdapter::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + MeshCapabilities LxmfAdapter::getCapabilities() const { MeshCapabilities caps; diff --git a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp index 01ca048b..1bfbd351 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshcore/meshcore_adapter.cpp @@ -16,6 +16,7 @@ #include "sys/event_bus.h" #include #include +#include #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -316,6 +318,25 @@ MeshCoreAdapter::MeshCoreAdapter(LoraBoard& board) self_hash_ = static_cast(node_id_ & 0xFF); } +void* MeshCoreAdapter::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MeshCoreAdapter::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MeshCoreAdapter::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + MeshCapabilities MeshCoreAdapter::getCapabilities() const { MeshCapabilities caps; diff --git a/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp index 4b790322..c98b6c88 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshtastic/mt_adapter.cpp @@ -15,12 +15,14 @@ #include "sys/event_bus.h" #include "team/protocol/team_portnum.h" #include +#include #include #include #include #include #include #include +#include #include #include #define TEST_CURVE25519_FIELD_OPS @@ -315,6 +317,25 @@ MtAdapter::~MtAdapter() { } +void* MtAdapter::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void MtAdapter::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void MtAdapter::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + bool MtAdapter::sendText(ChannelId channel, const std::string& text, MessageId* out_msg_id, NodeId peer) { diff --git a/platform/esp/arduino_common/src/chat/infra/meshtastic/node_store.cpp b/platform/esp/arduino_common/src/chat/infra/meshtastic/node_store.cpp index 0ce4bd60..4cb9ec96 100644 --- a/platform/esp/arduino_common/src/chat/infra/meshtastic/node_store.cpp +++ b/platform/esp/arduino_common/src/chat/infra/meshtastic/node_store.cpp @@ -13,7 +13,9 @@ #include #include #include +#include #include +#include #include namespace chat @@ -31,6 +33,25 @@ namespace meshtastic #define NODE_STORE_LOG(...) #endif +void* NodeStore::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void NodeStore::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void NodeStore::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + namespace { constexpr TickType_t kAsyncSaveMutexWait = pdMS_TO_TICKS(20); @@ -38,7 +59,7 @@ constexpr TickType_t kAsyncSavePollInterval = pdMS_TO_TICKS(10); constexpr TickType_t kAsyncSaveRetryDelay = pdMS_TO_TICKS(500); constexpr TickType_t kSdLoadWait = pdMS_TO_TICKS(250); constexpr TickType_t kSdPersistWait = pdMS_TO_TICKS(100); -constexpr uint32_t kAsyncSaveTaskStackBytes = 5 * 1024; +constexpr uint32_t kAsyncSaveTaskStackBytes = 4 * 1024; constexpr UBaseType_t kAsyncSaveTaskPriority = 2; constexpr size_t kSdReadChunkBytes = 256; diff --git a/platform/esp/arduino_common/src/chat/infra/rnode/rnode_adapter.cpp b/platform/esp/arduino_common/src/chat/infra/rnode/rnode_adapter.cpp index 43f14222..0ee251a2 100644 --- a/platform/esp/arduino_common/src/chat/infra/rnode/rnode_adapter.cpp +++ b/platform/esp/arduino_common/src/chat/infra/rnode/rnode_adapter.cpp @@ -8,10 +8,12 @@ #include "chat/time_utils.h" #include "platform/esp/arduino_common/app_tasks.h" #include +#include #include #include #include #include +#include namespace chat { @@ -47,6 +49,25 @@ RNodeAdapter::RNodeAdapter(LoraBoard& board) { } +void* RNodeAdapter::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void RNodeAdapter::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void RNodeAdapter::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + MeshCapabilities RNodeAdapter::getCapabilities() const { return MeshCapabilities{}; diff --git a/platform/esp/arduino_common/src/gps/gps_service.cpp b/platform/esp/arduino_common/src/gps/gps_service.cpp index a6991b55..bfc6770b 100644 --- a/platform/esp/arduino_common/src/gps/gps_service.cpp +++ b/platform/esp/arduino_common/src/gps/gps_service.cpp @@ -7,6 +7,8 @@ #include "platform/esp/arduino_common/power_tier.h" #include #include +#include +#include // GPS task loop diagnostics are high frequency and can stall touch/LVGL work on ESP. // Enable only while actively debugging receiver bring-up. @@ -43,8 +45,19 @@ namespace gps GpsService& GpsService::getInstance() { - static GpsService instance; - return instance; + static GpsService* instance = []() -> GpsService* + { + void* storage = heap_caps_malloc_prefer(sizeof(GpsService), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(GpsService)); + } + return new (storage) GpsService(); + }(); + return *instance; } void GpsService::begin(GpsBoard& gps_board, MotionBoard& motion_board, @@ -91,7 +104,7 @@ void GpsService::begin(GpsBoard& gps_board, MotionBoard& motion_board, BaseType_t task_result = xTaskCreate( gpsTask, "gps_collect", - 4 * 1024, + 3 * 1024, this, 5, &gps_task_handle_); @@ -112,7 +125,7 @@ void GpsService::begin(GpsBoard& gps_board, MotionBoard& motion_board, task_result = xTaskCreate( motionTask, "motion_mgr", - 3 * 1024, + 2 * 1024, this, 6, &motion_task_handle_); @@ -385,7 +398,7 @@ void GpsService::setMotionConfig(const MotionConfig& config) BaseType_t task_result = xTaskCreate( motionTask, "motion_mgr", - 3 * 1024, + 2 * 1024, this, 6, &motion_task_handle_); diff --git a/platform/esp/arduino_common/src/gps/track_recorder.cpp b/platform/esp/arduino_common/src/gps/track_recorder.cpp index 7b80f4d2..b4bf9007 100644 --- a/platform/esp/arduino_common/src/gps/track_recorder.cpp +++ b/platform/esp/arduino_common/src/gps/track_recorder.cpp @@ -3,7 +3,9 @@ #include "platform/esp/common/shared_spi_lock.h" #include +#include #include +#include namespace gps { @@ -124,8 +126,19 @@ void write_track_point(SdRuntimeFile& f, TrackFormat format, const TrackPoint& p TrackRecorder& TrackRecorder::getInstance() { - static TrackRecorder instance; - return instance; + static TrackRecorder* instance = []() -> TrackRecorder* + { + void* storage = heap_caps_malloc_prefer(sizeof(TrackRecorder), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(TrackRecorder)); + } + return new (storage) TrackRecorder(); + }(); + return *instance; } bool TrackRecorder::ensureDir() const diff --git a/platform/esp/arduino_common/src/mesh/esp_meshtastic_adapter_bridge.cpp b/platform/esp/arduino_common/src/mesh/esp_meshtastic_adapter_bridge.cpp index 1e3eb39e..3f5af7de 100644 --- a/platform/esp/arduino_common/src/mesh/esp_meshtastic_adapter_bridge.cpp +++ b/platform/esp/arduino_common/src/mesh/esp_meshtastic_adapter_bridge.cpp @@ -3,8 +3,10 @@ #include "platform/esp/arduino_common/app_tasks.h" #include +#include #include #include +#include namespace platform::esp::arduino_common::mesh { @@ -143,6 +145,25 @@ EspMeshtasticAdapterBridge::EspMeshtasticAdapterBridge(LoraBoard& board) (void)session_.start(config); } +void* EspMeshtasticAdapterBridge::operator new(std::size_t size) +{ + void* ptr = heap_caps_malloc_prefer(size, + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + return ptr != nullptr ? ptr : ::operator new(size); +} + +void EspMeshtasticAdapterBridge::operator delete(void* ptr) noexcept +{ + heap_caps_free(ptr); +} + +void EspMeshtasticAdapterBridge::operator delete(void* ptr, std::size_t) noexcept +{ + operator delete(ptr); +} + ::mesh::SendResult EspMeshtasticAdapterBridge::sendDirect(const ::mesh::DirectMessageCommand& command) { if (session_.state() != ::mesh::MeshSessionState::Ready) diff --git a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp index 75f3728e..0c841a21 100644 --- a/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp +++ b/platform/esp/arduino_common/src/ui/widgets/map/map_tiles.cpp @@ -72,6 +72,20 @@ static bool use_non_touch_placeholder_cards() return !::ui::page_profile::current().large_touch_hitbox; } +template +T* psram_preferred_static_instance() +{ + void* storage = heap_caps_malloc_prefer(sizeof(T), + 2, + MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT, + MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT); + if (storage == nullptr) + { + storage = ::operator new(sizeof(T)); + } + return new (storage) T(); +} + static int fmt_tile_coord(int32_t value) { // Tile coordinates are bounded by the zoom-domain we support, so narrowing @@ -216,8 +230,8 @@ class SdMapTileFileSystem final : public ui::map_tiles::IMapTileFileSystem }; #endif -constexpr std::size_t kMapTileCommandQueueCapacity = 48; -constexpr std::size_t kMapTileEventQueueCapacity = 48; +constexpr std::size_t kMapTileCommandQueueCapacity = 16; +constexpr std::size_t kMapTileEventQueueCapacity = 16; constexpr std::size_t kMapTileWorkerScratchBytes = 192U * 1024U; constexpr int kMapTileEventsPerUiDrain = 1; constexpr int kMapTileRequestsPerUiStep = 2; @@ -886,7 +900,7 @@ class MapTileAvailabilityMemory final return mutex_ != nullptr; } - static constexpr std::size_t kCapacity = 192; + static constexpr std::size_t kCapacity = 64; Entry entries_[kCapacity]{}; std::size_t next_replace_ = 0; SemaphoreHandle_t mutex_ = nullptr; @@ -894,8 +908,9 @@ class MapTileAvailabilityMemory final MapTileAvailabilityMemory& map_tile_availability_memory() { - static MapTileAvailabilityMemory memory; - return memory; + static MapTileAvailabilityMemory* memory = + psram_preferred_static_instance(); + return *memory; } class MapTileCommandQueue final : public ui::map_tiles::IMapTileCommandSink @@ -1320,7 +1335,7 @@ class EspMapTileWorkerBackend final : public ui::map_tiles::IMapTileWorkerBacken class LvglDecodedTileCache final : public ui::map_tiles::IMapTileDecoderCache { public: - static constexpr std::size_t kCapacity = 32; + static constexpr std::size_t kCapacity = 16; void clear() override { @@ -1497,8 +1512,9 @@ PathOnlyMapTileFileSystem& tile_file_system() LvglDecodedTileCache& decoded_tile_cache() { - static LvglDecodedTileCache cache; - return cache; + static LvglDecodedTileCache* cache = + psram_preferred_static_instance(); + return *cache; } ui::map_tiles::FilesystemMapTileSource& tile_source() @@ -1626,7 +1642,7 @@ class MapTileAsyncHost final const BaseType_t ok = xTaskCreate(taskThunk, "map_tile_worker", - 6144, + 4 * 1024, this, 1, &task_); @@ -1662,8 +1678,8 @@ class MapTileAsyncHost final MapTileAsyncHost& map_tile_async_host() { - static MapTileAsyncHost host; - return host; + static MapTileAsyncHost* host = psram_preferred_static_instance(); + return *host; } uint8_t clamp_tile_zoom(int z) diff --git a/platform/esp/boards/src/display/DisplayInterface.cpp b/platform/esp/boards/src/display/DisplayInterface.cpp index b9f05582..145a6efd 100644 --- a/platform/esp/boards/src/display/DisplayInterface.cpp +++ b/platform/esp/boards/src/display/DisplayInterface.cpp @@ -335,27 +335,10 @@ void LilyGoDispArduinoSPI::pushColorsLocked(uint16_t* data, uint32_t len) digitalWrite(_dc, HIGH); if (_transfer_config.rgb565_msb_first) { - // Keep pixel byte-order handling in one place: display drivers describe - // how RGB565 must be placed on the wire, and LVGL stays unaware of it. - constexpr size_t kChunkPixels = 96; - uint8_t chunk[kChunkPixels * sizeof(uint16_t)]; - uint32_t offset = 0; - while (offset < len) - { - size_t batch = len - offset; - if (batch > kChunkPixels) - { - batch = kChunkPixels; - } - for (size_t i = 0; i < batch; ++i) - { - const uint16_t pixel = data[offset + i]; - chunk[i * 2] = static_cast(pixel >> 8); - chunk[i * 2 + 1] = static_cast(pixel & 0xFF); - } - _spi->writeBytes(chunk, batch * sizeof(uint16_t)); - offset += static_cast(batch); - } + // Keep RGB565 wire ordering in the SPI driver instead of copying every + // flush through a tiny stack chunk. The Arduino core writes pixels in + // 16-bit MSB order for ILI9341/ST77xx-style panels. + _spi->writePixels(data, len * sizeof(uint16_t)); } else { diff --git a/platform/esp/idf_common/src/platform_ui_sstv_runtime.cpp b/platform/esp/idf_common/src/platform_ui_sstv_runtime.cpp index 374ddc5a..210d68a7 100644 --- a/platform/esp/idf_common/src/platform_ui_sstv_runtime.cpp +++ b/platform/esp/idf_common/src/platform_ui_sstv_runtime.cpp @@ -1,7 +1,6 @@ #include "platform/ui/sstv_runtime.h" -#include "boards/t_display_p4/board_profile.h" -#include "boards/tab5/tab5_board.h" +#include "board/BoardBase.h" #include "sstv/sstv_service.h" namespace @@ -26,19 +25,6 @@ platform::ui::sstv::State convert_state(::sstv::State state) } } -bool board_supports_sstv() -{ -#if defined(TRAIL_MATE_ESP_BOARD_TAB5) - return ::boards::tab5::Tab5Board::hasAudio() && - ::boards::tab5::Tab5Board::hasSdCard(); -#elif defined(TRAIL_MATE_ESP_BOARD_T_DISPLAY_P4) - return ::boards::t_display_p4::kBoardProfile.has_audio && - ::boards::t_display_p4::kBoardProfile.has_sdcard; -#else - return false; -#endif -} - } // namespace namespace platform::ui::sstv @@ -46,7 +32,7 @@ namespace platform::ui::sstv bool is_supported() { - return board_supports_sstv(); + return board.hasSstvAudioInput(); } bool start() diff --git a/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp b/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp index 98fe31e7..30e49146 100644 --- a/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp +++ b/platform/esp/idf_components/t_display_p4/trail_mate_t_display_p4_runtime.cpp @@ -26,6 +26,7 @@ #include "lvgl.h" #include "rm69a10_driver.h" #include "sdkconfig.h" +#include "soc/soc_caps.h" #include "ui/app_runtime.h" #include "ui/menu/menu_runtime.h" #include "ui/runtime/ui_feedback.h" @@ -68,6 +69,7 @@ constexpr uint8_t kGt9895MaxTouchFingerCount = 10; constexpr uint32_t kKeyboardI2cDelayUs = 5; constexpr uint32_t kKeyboardResetDelayMs = 10; +constexpr uint32_t kKeyboardPowerSettleDelayMs = 50; constexpr uint32_t kKeyboardMonitorIntervalMs = 2000; constexpr uint8_t kKeyboardAttachDebounceCount = 2; constexpr uint8_t kKeyboardDetachDebounceCount = 3; @@ -553,6 +555,11 @@ bool keyboard_read_sda() return gpio_get_level(static_cast(keyboard_module().sda)) != 0; } +bool keyboard_read_scl() +{ + return gpio_get_level(static_cast(keyboard_module().scl)) != 0; +} + bool configure_keyboard_i2c_pins() { const auto& kb = keyboard_module(); @@ -567,6 +574,9 @@ bool configure_keyboard_i2c_pins() sda_cfg.pull_up_en = GPIO_PULLUP_ENABLE; sda_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; sda_cfg.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + sda_cfg.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif if (gpio_config(&sda_cfg) != ESP_OK) { ESP_LOGW(kTag, "Keyboard software I2C SDA GPIO config failed"); @@ -575,10 +585,13 @@ bool configure_keyboard_i2c_pins() gpio_config_t scl_cfg{}; scl_cfg.pin_bit_mask = 1ULL << static_cast(kb.scl); - scl_cfg.mode = GPIO_MODE_OUTPUT_OD; + scl_cfg.mode = GPIO_MODE_INPUT_OUTPUT_OD; scl_cfg.pull_up_en = GPIO_PULLUP_ENABLE; scl_cfg.pull_down_en = GPIO_PULLDOWN_DISABLE; scl_cfg.intr_type = GPIO_INTR_DISABLE; +#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER + scl_cfg.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE; +#endif if (gpio_config(&scl_cfg) != ESP_OK) { ESP_LOGW(kTag, "Keyboard software I2C SCL GPIO config failed"); @@ -593,8 +606,8 @@ bool configure_keyboard_i2c_pins() void keyboard_i2c_start() { - keyboard_sda(true); keyboard_scl(true); + keyboard_sda(true); keyboard_i2c_delay(); keyboard_sda(false); keyboard_i2c_delay(); @@ -612,7 +625,40 @@ void keyboard_i2c_stop() keyboard_i2c_delay(); } -bool keyboard_i2c_write_byte(uint8_t value) +void keyboard_recover_i2c_bus(const char* reason) +{ + keyboard_sda(true); + keyboard_scl(true); + keyboard_i2c_delay(); + + const bool idle_sda_before = keyboard_read_sda(); + const bool idle_scl_before = keyboard_read_scl(); + int pulses = 0; + if (!idle_sda_before) + { + for (; pulses < 9 && !keyboard_read_sda(); ++pulses) + { + keyboard_scl(false); + keyboard_i2c_delay(); + keyboard_scl(true); + keyboard_i2c_delay(); + } + } + + keyboard_i2c_stop(); + ESP_LOGI(kTag, + "T-Display-P4 keyboard I2C bus ready reason=%s sda=%d scl=%d idle_before=%d/%d idle_after=%d/%d recover_pulses=%d", + reason ? reason : "unknown", + keyboard_module().sda, + keyboard_module().scl, + idle_sda_before, + idle_scl_before, + keyboard_read_sda(), + keyboard_read_scl(), + pulses); +} + +bool keyboard_i2c_write_byte_raw(uint8_t value) { for (int bit = 7; bit >= 0; --bit) { @@ -621,10 +667,14 @@ bool keyboard_i2c_write_byte(uint8_t value) keyboard_scl(true); keyboard_i2c_delay(); keyboard_scl(false); - keyboard_i2c_delay(); } keyboard_sda(true); + return true; +} + +bool keyboard_i2c_wait_ack() +{ keyboard_i2c_delay(); keyboard_scl(true); keyboard_i2c_delay(); @@ -634,6 +684,11 @@ bool keyboard_i2c_write_byte(uint8_t value) return ack; } +bool keyboard_i2c_write_byte(uint8_t value) +{ + return keyboard_i2c_write_byte_raw(value) && keyboard_i2c_wait_ack(); +} + uint8_t keyboard_i2c_read_byte(bool ack) { uint8_t value = 0; @@ -651,7 +706,6 @@ uint8_t keyboard_i2c_read_byte(bool ack) } keyboard_sda(!ack); - keyboard_i2c_delay(); keyboard_scl(true); keyboard_i2c_delay(); keyboard_scl(false); @@ -739,6 +793,16 @@ bool keyboard_module_supported() return boards::t_display_p4::TDisplayP4Board::profile().supports_keyboard_module; } +bool ensure_keyboard_module_power() +{ + if (!boards::t_display_p4::TDisplayP4Board::instance().ensureExternal3v3Power()) + { + return false; + } + vTaskDelay(pdMS_TO_TICKS(kKeyboardPowerSettleDelayMs)); + return true; +} + KeyboardProbeResult probe_keyboard_module_bus() { KeyboardProbeResult result{}; @@ -748,7 +812,7 @@ KeyboardProbeResult probe_keyboard_module_bus() return result; } - result.power_ready = boards::t_display_p4::TDisplayP4Board::instance().ensureExternal3v3Power(); + result.power_ready = ensure_keyboard_module_power(); if (!result.power_ready) { return result; @@ -759,6 +823,7 @@ KeyboardProbeResult probe_keyboard_module_bus() { return result; } + keyboard_recover_i2c_bus("probe"); const auto& kb = keyboard_module(); result.xl9555_ack = kb.xl9555 != 0 && keyboard_probe_device_address(kb.xl9555); @@ -832,13 +897,14 @@ void log_keyboard_i2c_scan(const char* reason) { return; } - if (!boards::t_display_p4::TDisplayP4Board::instance().ensureExternal3v3Power() || + if (!ensure_keyboard_module_power() || !configure_keyboard_i2c_pins()) { ESP_LOGW(kTag, "T-Display-P4 keyboard I2C scan skipped reason=%s power_or_gpio_failed", reason ? reason : "unknown"); return; } + keyboard_recover_i2c_bus(reason ? reason : "scan"); char found[192] = {}; size_t used = 0; @@ -900,6 +966,107 @@ void log_keyboard_i2c_scan(const char* reason) found_bq25896); } +void log_keyboard_hardware_i2c_scan(const char* reason) +{ + if (!keyboard_module_supported()) + { + return; + } + const auto& kb = keyboard_module(); + if (kb.sda < 0 || kb.scl < 0) + { + return; + } + + i2c_master_bus_config_t bus_cfg{}; + bus_cfg.i2c_port = -1; + bus_cfg.sda_io_num = static_cast(kb.sda); + bus_cfg.scl_io_num = static_cast(kb.scl); + bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT; + bus_cfg.glitch_ignore_cnt = 7; + bus_cfg.flags.enable_internal_pullup = true; + + i2c_master_bus_handle_t bus = nullptr; + esp_err_t bus_err = i2c_new_master_bus(&bus_cfg, &bus); +#if SOC_LP_I2C_SUPPORTED + if (bus_err == ESP_ERR_NOT_FOUND) + { + bus_cfg.i2c_port = SOC_HP_I2C_NUM; + bus_cfg.lp_source_clk = LP_I2C_SCLK_DEFAULT; + bus_err = i2c_new_master_bus(&bus_cfg, &bus); + } +#endif + if (bus_err != ESP_OK) + { + ESP_LOGW(kTag, + "T-Display-P4 keyboard HW I2C scan unavailable reason=%s sda=%d scl=%d err=%s", + reason ? reason : "unknown", + kb.sda, + kb.scl, + esp_err_to_name(bus_err)); + return; + } + + char found[192] = {}; + size_t used = 0; + int count = 0; + bool found_xl9555 = false; + bool found_tca8418 = false; + bool found_bq25896 = false; + for (uint8_t address = kKeyboardI2cScanFirstAddress; + address <= kKeyboardI2cScanLastAddress; + ++address) + { + if (i2c_master_probe(bus, address, 20) != ESP_OK) + { + continue; + } + + const int written = std::snprintf(found + used, + sizeof(found) - used, + "%s0x%02X", + count == 0 ? "" : " ", + static_cast(address)); + if (written > 0) + { + used += std::min(static_cast(written), + used < sizeof(found) ? sizeof(found) - used - 1 : 0); + } + ++count; + found_xl9555 = found_xl9555 || address == kb.xl9555; + found_tca8418 = found_tca8418 || address == kb.tca8418; + found_bq25896 = found_bq25896 || address == kb.bq25896; + } + + (void)i2c_del_master_bus(bus); + if (count == 0) + { + ESP_LOGW(kTag, + "T-Display-P4 keyboard HW I2C scan reason=%s sda=%d scl=%d found=none expected_xl9555=0x%02X expected_tca8418=0x%02X expected_bq25896=0x%02X", + reason ? reason : "unknown", + kb.sda, + kb.scl, + static_cast(kb.xl9555), + static_cast(kb.tca8418), + static_cast(kb.bq25896)); + return; + } + + ESP_LOGI(kTag, + "T-Display-P4 keyboard HW I2C scan reason=%s sda=%d scl=%d found_count=%d found=%s expected_xl9555=0x%02X:%d expected_tca8418=0x%02X:%d expected_bq25896=0x%02X:%d", + reason ? reason : "unknown", + kb.sda, + kb.scl, + count, + found, + static_cast(kb.xl9555), + found_xl9555, + static_cast(kb.tca8418), + found_tca8418, + static_cast(kb.bq25896), + found_bq25896); +} + void IRAM_ATTR keyboard_irq_isr(void* arg) { (void)arg; @@ -1058,11 +1225,12 @@ uint8_t mask_for_count(int count) bool configure_tca8418_keypad() { const auto& kb = keyboard_module(); - if (!boards::t_display_p4::TDisplayP4Board::instance().ensureExternal3v3Power() || + if (!ensure_keyboard_module_power() || !configure_keyboard_i2c_pins()) { return false; } + keyboard_recover_i2c_bus("tca8418_init"); const bool reset_ok = reset_tca8418_via_keyboard_expander(); if (reset_ok) @@ -1268,6 +1436,7 @@ bool init_keyboard_backend(bool log_missing = true) "T-Display-P4 keyboard module not detected; touch IME remains enabled"); log_keyboard_probe_result("startup_not_detected", probe, true); log_keyboard_i2c_scan("startup_not_detected"); + log_keyboard_hardware_i2c_scan("startup_not_detected"); } return false; } @@ -1280,6 +1449,7 @@ bool init_keyboard_backend(bool log_missing = true) "T-Display-P4 keyboard module present but TCA8418 is not usable; touch IME remains enabled"); log_keyboard_probe_result("startup_controller_not_ready", probe, true); log_keyboard_i2c_scan("startup_controller_not_ready"); + log_keyboard_hardware_i2c_scan("startup_controller_not_ready"); } return false; } @@ -1292,6 +1462,7 @@ bool init_keyboard_backend(bool log_missing = true) "T-Display-P4 keyboard controller init failed; touch IME remains enabled"); log_keyboard_probe_result("startup_init_failed", probe, true); log_keyboard_i2c_scan("startup_init_failed"); + log_keyboard_hardware_i2c_scan("startup_init_failed"); } return false; } diff --git a/platformio.ini b/platformio.ini index b6af5ee0..0fefa1e7 100644 --- a/platformio.ini +++ b/platformio.ini @@ -37,8 +37,6 @@ build_unflags = -std=gnu++11 build_flags = -D CORE_DEBUG_LEVEL=0 - ; Increase Arduino loopTask stack to handle LVGL + GPS map rendering - -D ARDUINO_LOOP_STACK_SIZE=16384 -std=gnu++17 -D LV_CONF_INCLUDE_SIMPLE -D LV_USE_SNAPSHOT=1 diff --git a/variants/lilygo_tlora_pager/envs/tlora_pager.ini b/variants/lilygo_tlora_pager/envs/tlora_pager.ini index 9478f0cd..38a05f70 100644 --- a/variants/lilygo_tlora_pager/envs/tlora_pager.ini +++ b/variants/lilygo_tlora_pager/envs/tlora_pager.ini @@ -8,6 +8,15 @@ build_src_filter = - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=12288 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 + -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 + -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 + -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -D DISPLAY_DRIVER_ST7796 -D SCREEN_WIDTH=480 -D SCREEN_HEIGHT=222 @@ -22,6 +31,14 @@ lib_deps = ${arduino_base.lib_deps} adafruit/Adafruit TCA8418 @ 1.0.2 adafruit/Adafruit BusIO @ 1.17.0 +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tdeck_pro + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite [env:tlora_pager_lr1121] extends = arduino_base @@ -33,6 +50,15 @@ build_src_filter = - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=12288 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 + -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 + -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 + -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -D DISPLAY_DRIVER_ST7796 -D SCREEN_WIDTH=480 -D SCREEN_HEIGHT=222 @@ -49,6 +75,14 @@ lib_deps = ${arduino_base.lib_deps} adafruit/Adafruit TCA8418 @ 1.0.2 adafruit/Adafruit BusIO @ 1.17.0 +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tdeck_pro + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite [env:tlora_pager_sx1262_debug] extends = arduino_base @@ -60,6 +94,15 @@ build_src_filter = - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=12288 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 + -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 + -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 + -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -D DISPLAY_DRIVER_ST7796 -D SCREEN_WIDTH=480 -D SCREEN_HEIGHT=222 @@ -74,3 +117,11 @@ lib_deps = ${arduino_base.lib_deps} adafruit/Adafruit TCA8418 @ 1.0.2 adafruit/Adafruit BusIO @ 1.17.0 +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tdeck_pro + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite diff --git a/variants/lilygo_twatch_s3/envs/lilygo_twatch_s3.ini b/variants/lilygo_twatch_s3/envs/lilygo_twatch_s3.ini index fb21713f..0b0497e0 100644 --- a/variants/lilygo_twatch_s3/envs/lilygo_twatch_s3.ini +++ b/variants/lilygo_twatch_s3/envs/lilygo_twatch_s3.ini @@ -9,9 +9,15 @@ build_src_filter = - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=8192 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_WATCH_S3 -DARDUINO_LILYGO_LORA_SX1262 @@ -28,6 +34,14 @@ build_flags = -I variants/lilygo_twatch_s3 lib_deps = ${arduino_base.lib_deps} +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tdeck_pro + boards_tlora_pager + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite [env:lilygo_twatch_s3_debug] extends = arduino_base @@ -40,9 +54,15 @@ build_src_filter = - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=8192 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_WATCH_S3 -DARDUINO_LILYGO_LORA_SX1262 @@ -62,3 +82,11 @@ build_flags = -I variants/lilygo_twatch_s3 lib_deps = ${arduino_base.lib_deps} +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tdeck_pro + boards_tlora_pager + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite diff --git a/variants/tdeck/envs/tdeck.ini b/variants/tdeck/envs/tdeck.ini index 38850fbe..fdd4b680 100644 --- a/variants/tdeck/envs/tdeck.ini +++ b/variants/tdeck/envs/tdeck.ini @@ -1,15 +1,22 @@ [env:tdeck] extends = arduino_base board = T-Deck -upload_speed = 921600 +upload_speed = 460800 +board_build.flash_mode = dio build_src_filter = +<*> - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=12288 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_DECK -DARDUINO_LILYGO_LORA_SX1262 @@ -28,19 +35,34 @@ build_flags = -I variants/tdeck lib_deps = ${arduino_base.lib_deps} +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck_pro + boards_tlora_pager + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite [env:tdeck_debug] extends = arduino_base board = T-Deck -upload_speed = 921600 +upload_speed = 460800 +board_build.flash_mode = dio build_src_filter = +<*> - build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=12288 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_DECK -DARDUINO_LILYGO_LORA_SX1262 @@ -59,3 +81,11 @@ build_flags = -I variants/tdeck lib_deps = ${arduino_base.lib_deps} +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck_pro + boards_tlora_pager + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite diff --git a/variants/tdeck_pro/envs/tdeck_pro.ini b/variants/tdeck_pro/envs/tdeck_pro.ini index bb6d7403..871bfcc4 100644 --- a/variants/tdeck_pro/envs/tdeck_pro.ini +++ b/variants/tdeck_pro/envs/tdeck_pro.ini @@ -4,9 +4,15 @@ board = T-Deck-Pro upload_speed = 921600 build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=8192 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_DECK_PRO -DARDUINO_USB_CDC_ON_BOOT=1 @@ -37,6 +43,14 @@ lib_deps = adafruit/Adafruit TCA8418 @ ^1.0.2 adafruit/Adafruit BusIO @ ^1.17.0 mikalhart/TinyGPSPlus @ 1.0.3 +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tlora_pager + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite [env:tdeck_pro_pcm512a] extends = arduino_base @@ -44,9 +58,15 @@ board = T-Deck-Pro upload_speed = 921600 build_flags = ${arduino_base.build_flags} + -DARDUINO_LOOP_STACK_SIZE=8192 -DBOARD_HAS_PSRAM=1 + -DCONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY=1 -DCONFIG_BT_NIMBLE_MEM_ALLOC_MODE_EXTERNAL=1 -DCONFIG_BT_NIMBLE_MAX_CONNECTIONS=1 + -DCONFIG_BT_NIMBLE_ROLE_CENTRAL_DISABLED + -DCONFIG_BT_NIMBLE_ROLE_OBSERVER_DISABLED + -DCONFIG_BT_NIMBLE_MAX_BONDS=1 + -DCONFIG_BT_NIMBLE_MAX_CCCDS=4 -DCONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=185 -DARDUINO_T_DECK_PRO -DARDUINO_USB_CDC_ON_BOOT=1 @@ -80,3 +100,11 @@ lib_deps = adafruit/Adafruit TCA8418 @ ^1.0.2 adafruit/Adafruit BusIO @ ^1.17.0 mikalhart/TinyGPSPlus @ 1.0.3 +lib_ignore = + ${arduino_base.lib_ignore} + boards_tdeck + boards_tlora_pager + boards_twatchs3 + boards_tab5 + boards_t_display_p4 + boards_t_echo_lite