T-Echo Card audio support initial stages - codec2 attempts

This commit is contained in:
pelgraine
2026-05-07 05:47:18 +10:00
parent 4b734f1bac
commit 708b96e0e8
109 changed files with 87144 additions and 24 deletions
+3 -1
View File
@@ -322,7 +322,9 @@ private:
AckTableEntry expected_ack_table[EXPECTED_ACK_TABLE_SIZE]; // circular table
int next_ack_idx;
#define ADVERT_PATH_TABLE_SIZE 1000
#ifndef ADVERT_PATH_TABLE_SIZE
#define ADVERT_PATH_TABLE_SIZE 1000
#endif
AdvertPath* advert_paths; // PSRAM-allocated in begin(), size = ADVERT_PATH_TABLE_SIZE
// Sent message repeat tracking
+72 -5
View File
@@ -894,7 +894,7 @@
#endif
// --- T-Echo Lite: CardKB keyboard, GxEPD2 e-ink, no touch ---
#if defined(LILYGO_TECHO_LITE)
#if defined(LILYGO_TECHO_LITE) || defined(LILYGO_TECHO_CARD)
#include "ContactsScreen.h"
#include "ChannelScreen.h"
#include "ChannelPickerScreen.h"
@@ -904,6 +904,12 @@
#include "LastHeardScreen.h"
#include "PathEditorScreen.h"
#ifdef LILYGO_TECHO_CARD
#include "TechoCardHomeScreen.h"
static TechoCardHomeScreen* _techoHome = nullptr;
static int _techoC2Debug = 0;
#endif
#ifdef MECK_CARDKB
#include "CardKBKeyboard.h"
static CardKBKeyboard cardkb;
@@ -1027,8 +1033,8 @@ static uint32_t _atoi(const char* sp) {
/* GLOBAL OBJECTS */
#ifdef DISPLAY_CLASS
#include "UITask.h"
#if HAS_GPS
#include "MapScreen.h" // After BLE PNGdec headers conflict with BLE if included earlier
#if HAS_GPS && !defined(LILYGO_TECHO_CARD)
#include "MapScreen.h" // After BLE -- PNGdec headers conflict with BLE if included earlier
#endif
UITask ui_task(&board, &serial_interface);
#endif
@@ -2101,6 +2107,16 @@ void setup() {
MESH_DEBUG_PRINTLN("setup() - about to call ui_task.begin()");
ui_task.begin(disp, &sensors, the_mesh.getNodePrefs());
MESH_DEBUG_PRINTLN("setup() - ui_task.begin() done");
// T-Echo Card: replace the generic HomeScreen with the 72x40 OLED home screen
#ifdef LILYGO_TECHO_CARD
{
_techoHome = new TechoCardHomeScreen(&ui_task, &rtc_clock,
the_mesh.getNodePrefs());
ui_task.setHomeScreen(_techoHome);
MESH_DEBUG_PRINTLN("setup() - TechoCardHomeScreen installed");
}
#endif
#endif
// ---------------------------------------------------------------------------
@@ -2387,6 +2403,12 @@ void setup() {
Serial.printf("setup() complete - free heap: %d, largest block: %d\n",
ESP.getFreeHeap(), ESP.getMaxAllocHeap());
#endif
{
void* p25 = malloc(25000); void* p10 = malloc(10000); void* p4 = malloc(4000);
Serial.printf("setup() heap probe: 25K=%s 10K=%s 4K=%s\n",
p25?"yes":"no", p10?"yes":"no", p4?"yes":"no");
if (p4) free(p4); if (p10) free(p10); if (p25) free(p25);
}
MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ===");
}
@@ -2414,10 +2436,49 @@ void otaResumeRadio() {
#endif
void loop() {
// T-Echo Card: lazy Codec2 init from shallow stack context.
// codec2_create needs ~3KB stack for FFT/trig init. The loop task
// has only 4KB total. Calling from render() (deep call chain) overflows.
// Calling from here (top of loop) works because stack depth is minimal.
#ifdef LILYGO_TECHO_CARD
if (_techoHome && _techoHome->needsCodec2()) {
void* probe = malloc(25000);
Serial.printf("Voice: heap probe 25K=%s, creating codec2...\n", probe?"yes":"no");
if (probe) free(probe);
Serial.flush();
struct CODEC2* c2 = codec2_create(VC_C2_MODE);
Serial.printf("Voice: codec2_create returned %p\n", c2);
Serial.flush();
if (c2) {
_techoHome->setCodec2Instance(c2);
void* postProbe = malloc(1000);
Serial.printf("Voice: set OK, post-probe=%s\n", postProbe?"yes":"no");
if (postProbe) free(postProbe);
Serial.flush();
_techoC2Debug = 99999; // Keep printing to test if yields prevent crash
}
}
#endif
#ifdef MECK_OTA_UPDATE
if (!otaRadioPaused) {
#endif
#ifdef LILYGO_TECHO_CARD
if (_techoC2Debug > 0) {
Serial.print("[pre-mesh]"); Serial.flush();
}
#endif
the_mesh.loop();
#ifdef LILYGO_TECHO_CARD
if (_techoC2Debug > 0) {
Serial.print("[post-mesh]"); Serial.flush();
}
#endif
#ifdef MECK_OTA_UPDATE
} else {
// OTA/File Manager active — poll the web server from the main loop for fast response.
@@ -2457,7 +2518,7 @@ void loop() {
// Map screen: periodically update own GPS position and contact markers
#ifdef DISPLAY_CLASS
#if HAS_GPS
#if HAS_GPS && !defined(LILYGO_TECHO_CARD)
if (ui_task.isOnMapScreen()) {
static unsigned long lastMapUpdate = 0;
if (millis() - lastMapUpdate > 30000) { // Every 30 seconds
@@ -2927,7 +2988,13 @@ void loop() {
}
}
#else
#ifdef LILYGO_TECHO_CARD
if (_techoC2Debug > 0) { Serial.print("[pre-ui]"); Serial.flush(); }
#endif
ui_task.loop();
#ifdef LILYGO_TECHO_CARD
if (_techoC2Debug > 0) { Serial.print("[post-ui]"); Serial.flush(); _techoC2Debug--; }
#endif
#endif
#endif
rtc_clock.tick();
@@ -3601,7 +3668,7 @@ void loop() {
}
#endif
// Low-power loop throttle yield CPU when lock screen is active.
// Low-power loop throttle -- yield CPU when lock screen is active.
// The RTOS idle task executes WFI (wait-for-interrupt) during delay(),
// dramatically reducing CPU power draw. 50 ms gives 20 loop cycles/sec
// which is ample for LoRa packet reception (radio has hardware FIFO).
@@ -2814,9 +2814,11 @@ public:
} else if (type == ROW_GPS_BAUD) {
_editPickerIdx--;
if (_editPickerIdx < 0) _editPickerIdx = GPS_BAUD_OPTION_COUNT - 1;
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
} else if (type == ROW_AUTO_LOCK) {
_editPickerIdx--;
if (_editPickerIdx < 0) _editPickerIdx = AUTO_LOCK_OPTION_COUNT - 1;
#endif
} else if (type == ROW_FONT_STYLE) {
_editPickerIdx--;
if (_editPickerIdx < 0) _editPickerIdx = MECK_FONT_STYLE_COUNT - 1;
@@ -2835,9 +2837,11 @@ public:
} else if (type == ROW_GPS_BAUD) {
_editPickerIdx++;
if (_editPickerIdx >= GPS_BAUD_OPTION_COUNT) _editPickerIdx = 0;
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
} else if (type == ROW_AUTO_LOCK) {
_editPickerIdx++;
if (_editPickerIdx >= AUTO_LOCK_OPTION_COUNT) _editPickerIdx = 0;
#endif
} else if (type == ROW_FONT_STYLE) {
_editPickerIdx++;
if (_editPickerIdx >= MECK_FONT_STYLE_COUNT) _editPickerIdx = 0;
@@ -2859,12 +2863,14 @@ public:
_editMode = EDIT_NONE;
Serial.printf("Settings: GPS baud set to %lu (reboot to apply)\n",
(unsigned long)_prefs->gps_baudrate);
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
} else if (type == ROW_AUTO_LOCK) {
_prefs->auto_lock_minutes = AUTO_LOCK_OPTIONS[_editPickerIdx];
the_mesh.savePrefs();
_editMode = EDIT_NONE;
Serial.printf("Settings: Auto lock = %s\n",
autoLockLabel(_prefs->auto_lock_minutes));
#endif
} else if (type == ROW_FONT_STYLE) {
_prefs->ui_font_style = _editPickerIdx;
the_mesh.savePrefs();
+20 -11
View File
@@ -1,7 +1,7 @@
#include "UITask.h"
#include <helpers/TxtDataHelpers.h>
#include "../MyMesh.h"
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
#include "NotesScreen.h"
#endif
#include "RepeaterAdminScreen.h"
@@ -11,7 +11,7 @@
#ifdef MECK_WEB_READER
#include "WebReaderScreen.h"
#endif
#if HAS_GPS
#if HAS_GPS && !defined(LILYGO_TECHO_CARD)
#include "MapScreen.h"
#endif
#include "target.h"
@@ -52,7 +52,7 @@
#include "ChannelScreen.h"
#include "ChannelPickerScreen.h"
#include "ContactsScreen.h"
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
#include "TextReaderScreen.h"
#endif
#include "SettingsScreen.h"
@@ -1342,7 +1342,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
((ChannelPickerScreen*)channel_picker_screen)->setChannelScreen((ChannelScreen*)channel_screen);
contacts_screen = new ContactsScreen(this, &rtc_clock);
((ContactsScreen*)contacts_screen)->setDMUnreadPtr(_dmUnread);
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
text_reader = new TextReaderScreen(this, node_prefs);
notes_screen = new NotesScreen(this, node_prefs);
#else
@@ -1365,7 +1365,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#ifdef HAS_4G_MODEM
sms_screen = new SMSScreen(this, node_prefs);
#endif
#if HAS_GPS
#if HAS_GPS && !defined(LILYGO_TECHO_CARD)
map_screen = new MapScreen(this);
#else
map_screen = nullptr;
@@ -1379,6 +1379,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
#endif
// Apply saved dark mode preference (both T-Deck Pro and T5S3)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
if (_node_prefs->dark_mode) {
::display.setDarkMode(true);
}
@@ -1387,6 +1388,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
if (_node_prefs->ui_font_style > 0) {
::display.setFontStyle(_node_prefs->ui_font_style);
}
#endif
setCurrScreen(splash);
}
@@ -1723,7 +1725,7 @@ void UITask::loop() {
c = checkDisplayOn(KEY_NEXT);
} else {
// Navigate back: reader reading→file list, file list→home, others→home
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
if (isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)text_reader;
if (reader && reader->isReading()) {
@@ -1890,23 +1892,27 @@ if (curr) curr->poll();
_next_refresh = millis() + 500; // Re-check in 500ms
} else {
// Sync dark mode with prefs (settings toggle takes effect here)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
if (_node_prefs && display.isDarkMode() != (_node_prefs->dark_mode != 0)) {
display.setDarkMode(_node_prefs->dark_mode != 0);
}
#endif
#if defined(LilyGo_T5S3_EPaper_Pro)
// Sync portrait mode with prefs (T5S3 only)
if (_node_prefs && display.isPortraitMode() != (_node_prefs->portrait_mode != 0)) {
display.setPortraitMode(_node_prefs->portrait_mode != 0);
// Text reader layout depends on orientation force recalculation
// Text reader layout depends on orientation -- force recalculation
if (text_reader) {
((TextReaderScreen*)text_reader)->invalidateLayout();
}
}
#endif
// Sync font style with prefs (settings toggle takes effect here)
#if defined(LilyGo_T5S3_EPaper_Pro) || defined(LilyGo_TDeck_Pro)
if (_node_prefs && display.getFontStyle() != _node_prefs->ui_font_style) {
display.setFontStyle(_node_prefs->ui_font_style);
}
#endif
_display->startFrame();
#if defined(LilyGo_T5S3_EPaper_Pro)
if (_vkbActive) {
@@ -2425,7 +2431,7 @@ void UITask::onVKBSubmit() {
break;
}
case VKB_NOTES: {
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
NotesScreen* notes = (NotesScreen*)getNotesScreen();
if (notes && strlen(text) > 0) {
for (int i = 0; text[i]; i++) {
@@ -2494,7 +2500,7 @@ void UITask::onVKBSubmit() {
}
#endif
case VKB_TEXT_PAGE: {
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
if (strlen(text) > 0) {
int pageNum = atoi(text);
TextReaderScreen* reader = (TextReaderScreen*)getTextReaderScreen();
@@ -2735,7 +2741,7 @@ void UITask::gotoContactsScreen() {
void UITask::gotoTextReader() {
if (!text_reader) return; // Not available on this platform
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
TextReaderScreen* reader = (TextReaderScreen*)text_reader;
if (_display != NULL) {
reader->enter(*_display);
@@ -2751,7 +2757,7 @@ void UITask::gotoTextReader() {
void UITask::gotoNotesScreen() {
if (!notes_screen) return; // Not available on this platform
#if !defined(LILYGO_TECHO_LITE)
#if !defined(LILYGO_TECHO_LITE) && !defined(LILYGO_TECHO_CARD)
NotesScreen* notes = (NotesScreen*)notes_screen;
if (_display != NULL) {
notes->enter(*_display);
@@ -3011,6 +3017,8 @@ void UITask::gotoWebReader() {
#if HAS_GPS
void UITask::gotoMapScreen() {
if (!map_screen) return; // Not available on this platform (T-Echo Card)
#if !defined(LILYGO_TECHO_CARD)
MapScreen* map = (MapScreen*)map_screen;
if (_display != NULL) {
map->enter(*_display);
@@ -3021,6 +3029,7 @@ void UITask::gotoMapScreen() {
}
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 100;
#endif
}
#endif
+1
View File
@@ -325,6 +325,7 @@ public:
NodePrefs* getNodePrefs() const { return _node_prefs; }
UIScreen* getAudiobookScreen() const { return audiobook_screen; }
void setAudiobookScreen(UIScreen* s) { audiobook_screen = s; }
void setHomeScreen(UIScreen* s) { if (home) delete home; home = s; }
#ifdef MECK_AUDIO_VARIANT
UIScreen* getAlarmScreen() const { return alarm_screen; }
void setAlarmScreen(UIScreen* s) { alarm_screen = s; }