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