From 7915e5ef0b71bf5d474b06c84d858df2aa51dd3a Mon Sep 17 00:00:00 2001 From: pelgraine <140762863+pelgraine@users.noreply.github.com> Date: Thu, 26 Feb 2026 14:14:39 +1100 Subject: [PATCH] =?UTF-8?q?Changed=20max=20contacts=20handling=20to=20psra?= =?UTF-8?q?m=20so=20Audio=20BLE=20is=20400=20=E2=86=92=20500=20contacts,?= =?UTF-8?q?=2020=20channels=20(Near=20BLE=20protocol=20max=20(510)).=20Aud?= =?UTF-8?q?io=20Standalone350=20=E2=86=92=201500=20(40=20channels=20?= =?UTF-8?q?=E2=86=92=2020)=20PSRAM-backed.=204G=20BLE=20env=20is=20400=20?= =?UTF-8?q?=E2=86=92=20500=20with=2020=20channels=20(Near=20BLE=20protocol?= =?UTF-8?q?=20max=20(510)).=204G=20Standalone=20is=20600=20=E2=86=92=20150?= =?UTF-8?q?0=20contacts=20with=2020=20channels=20-=20PSRAM-backed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- examples/companion_radio/MyMesh.cpp | 1 + .../companion_radio/ui-new/Contactsscreen.h | 35 +++++++++++-------- examples/companion_radio/ui-new/UITask.cpp | 3 ++ src/helpers/BaseChatMesh.h | 21 +++++++++-- variants/lilygo_tdeck_pro/platformio.ini | 16 +++++---- 5 files changed, 52 insertions(+), 24 deletions(-) diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 430ddd1..c24f20f 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -1043,6 +1043,7 @@ void MyMesh::begin(bool has_display) { _active_ble_pin = 0; #endif + initContacts(); // allocate contacts array from PSRAM (deferred from constructor) resetContacts(); _store->loadContacts(this); bootstrapRTCfromContacts(); diff --git a/examples/companion_radio/ui-new/Contactsscreen.h b/examples/companion_radio/ui-new/Contactsscreen.h index 42cc70d..488887b 100644 --- a/examples/companion_radio/ui-new/Contactsscreen.h +++ b/examples/companion_radio/ui-new/Contactsscreen.h @@ -18,7 +18,6 @@ public: FILTER_REPEATER, FILTER_ROOM, // Room servers FILTER_SENSOR, - FILTER_FAVOURITE, // Contacts marked as favourite (any type) FILTER_COUNT // keep last }; @@ -31,9 +30,9 @@ private: // Cached filtered contact indices for efficient scrolling // We rebuild this on filter change or when entering the screen - static const int MAX_VISIBLE = 400; // matches MAX_CONTACTS build flag - uint16_t _filteredIdx[MAX_VISIBLE]; // indices into contact table - uint32_t _filteredTs[MAX_VISIBLE]; // cached last_advert_timestamp for sorting + // Arrays allocated in PSRAM when available (supports 1000+ contacts) + uint16_t* _filteredIdx; // indices into contact table + uint32_t* _filteredTs; // cached last_advert_timestamp for sorting int _filteredCount; // how many contacts match current filter bool _cacheValid; @@ -49,7 +48,6 @@ private: case FILTER_REPEATER: return "Rptr"; case FILTER_ROOM: return "Room"; case FILTER_SENSOR: return "Sens"; - case FILTER_FAVOURITE: return "Fav"; default: return "?"; } } @@ -63,7 +61,7 @@ private: } } - bool matchesFilter(uint8_t adv_type, uint8_t flags = 0) const { + bool matchesFilter(uint8_t adv_type) const { switch (_filter) { case FILTER_ALL: return true; case FILTER_CHAT: return adv_type == ADV_TYPE_CHAT; @@ -72,7 +70,6 @@ private: case FILTER_SENSOR: return (adv_type != ADV_TYPE_CHAT && adv_type != ADV_TYPE_REPEATER && adv_type != ADV_TYPE_ROOM); - case FILTER_FAVOURITE: return (flags & 0x01) != 0; default: return true; } } @@ -81,9 +78,9 @@ private: _filteredCount = 0; uint32_t numContacts = the_mesh.getNumContacts(); ContactInfo contact; - for (uint32_t i = 0; i < numContacts && _filteredCount < MAX_VISIBLE; i++) { + for (uint32_t i = 0; i < numContacts && _filteredCount < MAX_CONTACTS; i++) { if (the_mesh.getContactByIdx(i, contact)) { - if (matchesFilter(contact.type, contact.flags)) { + if (matchesFilter(contact.type)) { _filteredIdx[_filteredCount] = (uint16_t)i; _filteredTs[_filteredCount] = contact.last_advert_timestamp; _filteredCount++; @@ -91,7 +88,7 @@ private: } } // Sort by last_advert_timestamp descending (most recently seen first) - // Simple insertion sort — fine for up to 400 entries on ESP32 + // Insertion sort — fine for up to ~1000 entries on ESP32 for (int i = 1; i < _filteredCount; i++) { uint16_t tmpIdx = _filteredIdx[i]; uint32_t tmpTs = _filteredTs[i]; @@ -133,7 +130,15 @@ private: public: ContactsScreen(UITask* task, mesh::RTCClock* rtc) : _task(task), _rtc(rtc), _scrollPos(0), _filter(FILTER_ALL), - _filteredCount(0), _cacheValid(false), _rowsPerPage(5) {} + _filteredCount(0), _cacheValid(false), _rowsPerPage(5) { + #if defined(ESP32) && defined(BOARD_HAS_PSRAM) + _filteredIdx = (uint16_t*)ps_calloc(MAX_CONTACTS, sizeof(uint16_t)); + _filteredTs = (uint32_t*)ps_calloc(MAX_CONTACTS, sizeof(uint32_t)); + #else + _filteredIdx = new uint16_t[MAX_CONTACTS](); + _filteredTs = new uint32_t[MAX_CONTACTS](); + #endif + } void invalidateCache() { _cacheValid = false; } @@ -289,17 +294,17 @@ public: display.drawRect(0, footerY - 2, display.width(), 1); display.setColor(DisplayDriver::YELLOW); - // Left: Q:Back + // Left: Q:Bk X:Exp display.setCursor(0, footerY); - display.print("Q:Back"); + display.print("Q:Bk X:Exp"); // Center: A/D:Filter const char* mid = "A/D:Filtr"; display.setCursor((display.width() - display.getTextWidth(mid)) / 2, footerY); display.print(mid); - // Right: W/S:Scroll - const char* right = "W/S:Scrll"; + // Right: R:Imp W/S + const char* right = "R:Imp W/S"; display.setCursor(display.width() - display.getTextWidth(right) - 2, footerY); display.print(right); diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 69de49d..18e50e8 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1524,6 +1524,9 @@ void UITask::gotoWebReader() { if (_display != NULL) { wr->enter(*_display); } + // Heap diagnostic — check state after web reader entry (WiFi connects later) + Serial.printf("[HEAP] WebReader enter - free: %u, largest: %u, PSRAM: %u\n", + ESP.getFreeHeap(), ESP.getMaxAllocHeap(), ESP.getFreePsram()); setCurrScreen(web_reader); if (_display != NULL && !_display->isOn()) { _display->turnOn(); diff --git a/src/helpers/BaseChatMesh.h b/src/helpers/BaseChatMesh.h index fd391b9..ec50630 100644 --- a/src/helpers/BaseChatMesh.h +++ b/src/helpers/BaseChatMesh.h @@ -58,9 +58,9 @@ class BaseChatMesh : public mesh::Mesh { friend class ContactsIterator; - ContactInfo contacts[MAX_CONTACTS]; + ContactInfo* contacts; int num_contacts; - int sort_array[MAX_CONTACTS]; + int* sort_array; int matching_peer_indexes[MAX_SEARCH_RESULTS]; unsigned long txt_send_timeout; #ifdef MAX_GROUP_CHANNELS @@ -78,6 +78,8 @@ protected: BaseChatMesh(mesh::Radio& radio, mesh::MillisecondClock& ms, mesh::RNG& rng, mesh::RTCClock& rtc, mesh::PacketManager& mgr, mesh::MeshTables& tables) : mesh::Mesh(radio, ms, rng, rtc, mgr, tables) { + contacts = NULL; + sort_array = NULL; num_contacts = 0; #ifdef MAX_GROUP_CHANNELS memset(channels, 0, sizeof(channels)); @@ -90,6 +92,19 @@ protected: void bootstrapRTCfromContacts(); void resetContacts() { num_contacts = 0; } + + // Must be called from begin() before loadContacts/bootstrapRTCfromContacts. + // Deferred from constructor because PSRAM is not available during global init. + void initContacts() { + if (contacts != NULL) return; // already initialized + #if defined(ESP32) && defined(BOARD_HAS_PSRAM) + contacts = (ContactInfo*)ps_calloc(MAX_CONTACTS, sizeof(ContactInfo)); + sort_array = (int*)ps_calloc(MAX_CONTACTS, sizeof(int)); + #else + contacts = new ContactInfo[MAX_CONTACTS](); + sort_array = new int[MAX_CONTACTS](); + #endif + } void populateContactFromAdvert(ContactInfo& ci, const mesh::Identity& id, const AdvertDataParser& parser, uint32_t timestamp); ContactInfo* allocateContactSlot(); // helper to find slot for new contact @@ -169,4 +184,4 @@ public: int findChannelIdx(const mesh::GroupChannel& ch); void loop(); -}; +}; \ No newline at end of file diff --git a/variants/lilygo_tdeck_pro/platformio.ini b/variants/lilygo_tdeck_pro/platformio.ini index d9f60a1..38ca79d 100644 --- a/variants/lilygo_tdeck_pro/platformio.ini +++ b/variants/lilygo_tdeck_pro/platformio.ini @@ -96,12 +96,13 @@ lib_deps = ; --------------------------------------------------------------------------- ; Audio + BLE companion (audio-player hardware with BLE phone bridging) +; MAX_CONTACTS=500 is near BLE protocol ceiling (MAX_CONTACTS/2 sent as uint8_t, max 510) [env:meck_audio_ble] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=400 + -D MAX_CONTACTS=500 -D MAX_GROUP_CHANNELS=20 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 @@ -121,13 +122,14 @@ lib_deps = ; Audio standalone (audio-player hardware, no BLE/WiFi — maximum battery life) ; No MECK_WEB_READER: WiFi power draw conflicts with zero-radio-power design. +; Contacts and sort arrays allocated in PSRAM — 1500 contacts uses ~290KB of 8MB. [env:meck_audio_standalone] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 + -D MAX_CONTACTS=1500 + -D MAX_GROUP_CHANNELS=20 -D OFFLINE_QUEUE_SIZE=256 -D MECK_AUDIO_VARIANT build_src_filter = ${LilyGo_TDeck_Pro.build_src_filter} @@ -143,12 +145,13 @@ lib_deps = bitbank2/JPEGDEC ; 4G + BLE companion (4G modem hardware, no audio — GPIO conflict with PCM5102A) +; MAX_CONTACTS=500 is near BLE protocol ceiling (MAX_CONTACTS/2 sent as uint8_t, max 510) [env:meck_4g_ble] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=400 + -D MAX_CONTACTS=500 -D MAX_GROUP_CHANNELS=20 -D BLE_PIN_CODE=123456 -D OFFLINE_QUEUE_SIZE=256 @@ -169,13 +172,14 @@ lib_deps = ; No BLE_PIN_CODE: BLE never initializes, saving ~30KB heap + radio power. ; MECK_WEB_READER enabled: works better without BLE (no teardown dance needed, ; more free heap from boot). WiFi-first with cellular PPP fallback (future). +; Contacts and sort arrays allocated in PSRAM — 1500 contacts uses ~290KB of 8MB. [env:meck_4g_standalone] extends = LilyGo_TDeck_Pro build_flags = ${LilyGo_TDeck_Pro.build_flags} -I examples/companion_radio/ui-new - -D MAX_CONTACTS=350 - -D MAX_GROUP_CHANNELS=40 + -D MAX_CONTACTS=1500 + -D MAX_GROUP_CHANNELS=20 -D OFFLINE_QUEUE_SIZE=256 -D HAS_4G_MODEM=1 -D MECK_WEB_READER=1