mirror of
https://github.com/pelgraine/Meck.git
synced 2026-08-08 01:32:42 +02:00
initial screen based t-echo lite with card kb support build
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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() {
|
||||
|
||||
Reference in New Issue
Block a user