initial screen based t-echo lite with card kb support build

This commit is contained in:
pelgraine
2026-04-21 14:07:06 +10:00
parent 291c42a40e
commit db0ecd3c58
18 changed files with 902 additions and 19 deletions
+4 -2
View File
@@ -9,7 +9,8 @@
DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(nullptr), _clock(&clock),
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
identity_store(fs, "")
identity_store(fs, ""),
_saveFile(fs)
#elif defined(RP2040_PLATFORM)
identity_store(fs, "/identity")
#else
@@ -21,7 +22,8 @@ DataStore::DataStore(FILESYSTEM& fs, mesh::RTCClock& clock) : _fs(&fs), _fsExtra
#if defined(EXTRAFS) || defined(QSPIFLASH)
DataStore::DataStore(FILESYSTEM& fs, FILESYSTEM& fsExtra, mesh::RTCClock& clock) : _fs(&fs), _fsExtra(&fsExtra), _clock(&clock),
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
identity_store(fs, "")
identity_store(fs, ""),
_saveFile(fs)
#elif defined(RP2040_PLATFORM)
identity_store(fs, "/identity")
#else
+2 -2
View File
@@ -8,11 +8,11 @@
#define FIRMWARE_VER_CODE 11
#ifndef FIRMWARE_BUILD_DATE
#define FIRMWARE_BUILD_DATE "19 April 2026"
#define FIRMWARE_BUILD_DATE "21 April 2026"
#endif
#ifndef FIRMWARE_VERSION
#define FIRMWARE_VERSION "Meck v1.7"
#define FIRMWARE_VERSION "Meck v1.7.1"
#endif
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
+82 -8
View File
@@ -1,8 +1,8 @@
#include <Arduino.h> // needed for PlatformIO
#ifdef BLE_PIN_CODE
#if defined(ESP32) && defined(BLE_PIN_CODE)
#include <esp_bt.h> // for esp_bt_controller_mem_release (web reader WiFi)
#endif
#ifdef MECK_OTA_UPDATE
#if defined(ESP32) && defined(MECK_OTA_UPDATE)
#include <esp_ota_ops.h>
#endif
#include <Mesh.h>
@@ -890,11 +890,37 @@
}
#endif
// --- T-Echo Lite: CardKB keyboard, GxEPD2 e-ink, no touch ---
#if defined(LILYGO_TECHO_LITE)
#include "ContactsScreen.h"
#include "ChannelScreen.h"
#include "ChannelPickerScreen.h"
#include "SettingsScreen.h"
#include "RepeaterAdminScreen.h"
#include "DiscoveryScreen.h"
#include "LastHeardScreen.h"
#include "PathEditorScreen.h"
#ifdef MECK_CARDKB
#include "CardKBKeyboard.h"
static CardKBKeyboard cardkb;
static unsigned long lastCardKBProbe = 0;
#define CARDKB_PROBE_INTERVAL_MS 5000
#endif
#endif
// Board-agnostic: CPU frequency scaling and AGC reset
CPUPowerManager cpuPower;
#define AGC_RESET_INTERVAL_MS 500
static unsigned long lastAGCReset = 0;
// nRF52 RAM diagnostic
extern "C" char *sbrk(int incr);
static int dbg_free_ram() {
char top;
return &top - reinterpret_cast<char*>(sbrk(0));
}
// Believe it or not, this std C function is busted on some platforms!
static uint32_t _atoi(const char* sp) {
uint32_t n = 0;
@@ -2089,9 +2115,11 @@ void setup() {
#endif
// Initialize CardKB external keyboard (if connected via QWIIC)
#if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB)
#if defined(MECK_CARDKB)
if (cardkb.begin()) {
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.setCardKBDetected(true);
#endif
Serial.println("setup() - CardKB detected at 0x5F");
} else {
Serial.println("setup() - CardKB not detected (will re-probe)");
@@ -2317,8 +2345,10 @@ void setup() {
the_mesh.setVoiceEnvelopeHandler(voiceEnvelopeCallback);
#endif
Serial.printf("setup() complete — free heap: %d, largest block: %d\n",
#ifdef ESP32
Serial.printf("setup() complete - free heap: %d, largest block: %d\n",
ESP.getFreeHeap(), ESP.getMaxAllocHeap());
#endif
MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ===");
}
@@ -2851,6 +2881,17 @@ void loop() {
#endif
#endif
rtc_clock.tick();
// --- T-Echo Lite runtime diagnostic (remove after debugging) ---
{
static unsigned long lastDbg = 0;
if (millis() - lastDbg > 2000) {
Serial.printf("loop alive - free RAM: %d, screen: %s\n",
dbg_free_ram(),
ui_task.isOnHomeScreen() ? "home" : "other");
lastDbg = millis();
}
}
// Periodic AGC reset - re-assert boosted RX gain to prevent sensitivity drift
#ifdef MECK_OTA_UPDATE
if (!otaRadioPaused)
@@ -3130,12 +3171,12 @@ void loop() {
#endif // MECK_TOUCH_ENABLED
// ---------------------------------------------------------------------------
// CardKB external keyboard polling (T5S3 only, via QWIIC)
// CardKB external keyboard polling (via QWIIC)
// When VKB is active: typed characters feed into the VKB text buffer.
// When VKB is not active: navigation keys route through injectKey().
// ESC key maps to 'q' (back) when no VKB is active.
// ---------------------------------------------------------------------------
#if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB)
#if defined(MECK_CARDKB)
{
// Hot-plug detection: re-probe periodically
if (millis() - lastCardKBProbe >= CARDKB_PROBE_INTERVAL_MS) {
@@ -3143,7 +3184,9 @@ void loop() {
bool wasDetected = cardkb.isDetected();
bool nowDetected = cardkb.probe();
if (nowDetected != wasDetected) {
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.setCardKBDetected(nowDetected);
#endif
Serial.printf("[CardKB] %s\n", nowDetected ? "Connected" : "Disconnected");
}
}
@@ -3151,15 +3194,22 @@ void loop() {
// Poll for keypress
char ckb = cardkb.readKey();
if (ckb != 0) {
// Block input while locked (same as touch)
// Block input while locked (T5S3 only — T-Echo Lite has no lock screen yet)
#if defined(LilyGo_T5S3_EPaper_Pro)
if (!ui_task.isLocked()) {
#else
{
#endif
cpuPower.setBoost();
ui_task.keepAlive();
#if defined(LilyGo_T5S3_EPaper_Pro)
if (ui_task.isVKBActive()) {
// VKB is open — feed character into VKB text buffer
ui_task.feedCardKBChar(ckb);
} else if (ui_task.isOnHomeScreen()) {
} else
#endif
if (ui_task.isOnHomeScreen()) {
// Home screen: ESC does nothing special, letter shortcuts open tiles
if (ckb == 0x1B) {
// ESC on home — no-op (already home)
@@ -3167,8 +3217,10 @@ void loop() {
switch (ckb) {
case 'm': ui_task.gotoChannelPickerScreen(); break;
case 'c': ui_task.gotoContactsScreen(); break;
#if !defined(LILYGO_TECHO_LITE)
case 'e': ui_task.gotoTextReader(); break;
case 'n': ui_task.gotoNotesScreen(); break;
#endif
case 's': ui_task.gotoSettingsScreen(); break;
case 'f': ui_task.gotoDiscoveryScreen(); break;
case 'h': ui_task.gotoLastHeardScreen(); break;
@@ -3187,6 +3239,7 @@ void loop() {
// Notes editing/renaming: route ALL keys directly (no VKB).
// This gives: Enter=newline, arrows=cursor, printable=insert, ESC=save&exit
#if !defined(LILYGO_TECHO_LITE)
if (ui_task.isOnNotesScreen()) {
NotesScreen* notesScr = (NotesScreen*)ui_task.getNotesScreen();
if (notesScr && (notesScr->isEditing() || notesScr->isRenaming())) {
@@ -3214,6 +3267,7 @@ void loop() {
ui_task.forceRefresh();
}
}
#endif
if (!handled) {
// ESC → back (same as 'q' on T-Deck Pro) for all non-notes screens
@@ -3253,7 +3307,11 @@ void loop() {
if (the_mesh.getContactByIdx(j, ci) && strcmp(ci.name, dmName) == 0) {
char label[40];
snprintf(label, sizeof(label), "DM: %s", dmName);
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.showVirtualKeyboard(VKB_DM, label, "", 137, j);
#else
ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler
#endif
ui_task.clearDMUnread(j);
break;
}
@@ -3266,7 +3324,11 @@ void loop() {
if (the_mesh.getChannel(chIdx, ch)) {
char label[40];
snprintf(label, sizeof(label), "To: %s", ch.name);
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.showVirtualKeyboard(VKB_CHANNEL_MSG, label, "", 137, chIdx);
#else
ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler
#endif
}
}
} else if (ui_task.isOnContactsScreen()) {
@@ -3290,7 +3352,11 @@ void loop() {
cs->getSelectedContactName(dname, sizeof(dname));
char label[40];
snprintf(label, sizeof(label), "DM: %s", dname);
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.showVirtualKeyboard(VKB_DM, label, "", 137, idx);
#else
ui_task.injectKey('\r'); // T-Echo Lite: compose via native handler
#endif
}
} else if (idx >= 0 && ctype == ADV_TYPE_REPEATER) {
ui_task.gotoRepeaterAdmin(idx);
@@ -3310,9 +3376,17 @@ void loop() {
if (admin) {
RepeaterAdminScreen::AdminState astate = admin->getState();
if (astate == RepeaterAdminScreen::STATE_PASSWORD_ENTRY) {
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.showVirtualKeyboard(VKB_ADMIN_PASSWORD, "Admin Password", "", 32);
#else
ui_task.injectKey('\r');
#endif
} else {
#if defined(LilyGo_T5S3_EPaper_Pro)
ui_task.showVirtualKeyboard(VKB_ADMIN_CLI, "Admin Command", "", 137);
#else
ui_task.injectKey('\r');
#endif
}
}
} else if (ui_task.isOnPathEditor()) {
@@ -5,19 +5,22 @@
// Polls 0x5F on the shared I2C bus via QWIIC connector.
// Maps CardKB special key codes to Meck key constants.
//
// Platform support:
// - ESP32/ESP32-S3 (T5S3, T-Deck Pro): Wire.begin(SDA, SCL)
// - nRF52840 (T-Echo Lite): Wire.begin() uses variant.h pins
//
// Usage:
// CardKBKeyboard cardkb;
// if (cardkb.begin()) { /* detected */ }
// char key = cardkb.readKey(); // returns 0 if no key
// =============================================================================
#if defined(LilyGo_T5S3_EPaper_Pro) && defined(MECK_CARDKB)
#if defined(MECK_CARDKB)
#ifndef CARDKB_KEYBOARD_H
#define CARDKB_KEYBOARD_H
#include <Arduino.h>
#include <Wire.h>
#include "variant.h" // For I2C_SDA, I2C_SCL (bus recovery)
// I2C address (defined in variant.h, fallback here)
#ifndef CARDKB_I2C_ADDR
@@ -75,7 +78,13 @@ public:
_errorCount++;
if (_errorCount >= 3) {
// I2C bus may be stuck — re-init to recover
#if defined(ESP32)
// ESP32: Wire.begin() accepts explicit SDA/SCL pins
Wire.begin(I2C_SDA, I2C_SCL);
#else
// nRF52: Wire.begin() uses PIN_WIRE_SDA/SCL from variant.h
Wire.begin();
#endif
Wire.setClock(100000);
_pollInterval = 500; // Back off for 500ms
_errorCount = 0;
@@ -119,4 +128,4 @@ private:
};
#endif // CARDKB_KEYBOARD_H
#endif // LilyGo_T5S3_EPaper_Pro && MECK_CARDKB
#endif // MECK_CARDKB
+23 -1
View File
@@ -1,7 +1,9 @@
#include "UITask.h"
#include <helpers/TxtDataHelpers.h>
#include "../MyMesh.h"
#if !defined(LILYGO_TECHO_LITE)
#include "NotesScreen.h"
#endif
#include "RepeaterAdminScreen.h"
#include "PathEditorScreen.h"
#include "DiscoveryScreen.h"
@@ -56,7 +58,9 @@
#include "ChannelScreen.h"
#include "ChannelPickerScreen.h"
#include "ContactsScreen.h"
#if !defined(LILYGO_TECHO_LITE)
#include "TextReaderScreen.h"
#endif
#include "SettingsScreen.h"
#ifdef MECK_AUDIO_VARIANT
#include "AudiobookPlayerScreen.h"
@@ -1300,8 +1304,13 @@ 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)
text_reader = new TextReaderScreen(this, node_prefs);
notes_screen = new NotesScreen(this, node_prefs);
#else
text_reader = nullptr; // T-Echo Lite: excluded to save RAM (256KB nRF52)
notes_screen = nullptr;
#endif
settings_screen = new SettingsScreen(this, &rtc_clock, node_prefs);
repeater_admin = nullptr; // Lazy-initialized on first use to preserve heap for audio
path_editor = nullptr; // Lazy-initialized on first use from contacts screen
@@ -1678,6 +1687,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 (isOnTextReader()) {
TextReaderScreen* reader = (TextReaderScreen*)text_reader;
if (reader && reader->isReading()) {
@@ -1695,7 +1705,9 @@ void UITask::loop() {
gotoHomeScreen();
}
c = 0;
} else if (isOnChannelPickerScreen()) {
} else
#endif
if (isOnChannelPickerScreen()) {
gotoHomeScreen(); // picker → home
c = 0;
} else if (isOnChannelScreen()) {
@@ -2336,12 +2348,14 @@ void UITask::onVKBSubmit() {
break;
}
case VKB_NOTES: {
#if !defined(LILYGO_TECHO_LITE)
NotesScreen* notes = (NotesScreen*)getNotesScreen();
if (notes && strlen(text) > 0) {
for (int i = 0; text[i]; i++) {
notes->handleInput(text[i]);
}
}
#endif
if (_screenBeforeVKB) setCurrScreen(_screenBeforeVKB);
break;
}
@@ -2403,6 +2417,7 @@ void UITask::onVKBSubmit() {
}
#endif
case VKB_TEXT_PAGE: {
#if !defined(LILYGO_TECHO_LITE)
if (strlen(text) > 0) {
int pageNum = atoi(text);
TextReaderScreen* reader = (TextReaderScreen*)getTextReaderScreen();
@@ -2410,6 +2425,7 @@ void UITask::onVKBSubmit() {
reader->gotoPage(pageNum);
}
}
#endif
if (_screenBeforeVKB) setCurrScreen(_screenBeforeVKB);
break;
}
@@ -2622,6 +2638,8 @@ void UITask::gotoContactsScreen() {
}
void UITask::gotoTextReader() {
if (!text_reader) return; // Not available on this platform
#if !defined(LILYGO_TECHO_LITE)
TextReaderScreen* reader = (TextReaderScreen*)text_reader;
if (_display != NULL) {
reader->enter(*_display);
@@ -2632,9 +2650,12 @@ void UITask::gotoTextReader() {
}
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 100;
#endif
}
void UITask::gotoNotesScreen() {
if (!notes_screen) return; // Not available on this platform
#if !defined(LILYGO_TECHO_LITE)
NotesScreen* notes = (NotesScreen*)notes_screen;
if (_display != NULL) {
notes->enter(*_display);
@@ -2649,6 +2670,7 @@ void UITask::gotoNotesScreen() {
}
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 100;
#endif
}
void UITask::gotoSettingsScreen() {