mirror of
https://github.com/pelgraine/Meck.git
synced 2026-03-28 17:42:44 +01:00
677 lines
20 KiB
C++
677 lines
20 KiB
C++
#include <Arduino.h> // needed for PlatformIO
|
|
#include <Mesh.h>
|
|
#include "MyMesh.h"
|
|
#include "variant.h" // Board-specific defines (HAS_GPS, etc.)
|
|
#include "target.h" // For sensors, board, etc.
|
|
|
|
// T-Deck Pro Keyboard support
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
#include "TCA8418Keyboard.h"
|
|
TCA8418Keyboard keyboard(I2C_ADDR_KEYBOARD, &Wire);
|
|
|
|
// Compose mode state
|
|
static bool composeMode = false;
|
|
static char composeBuffer[138]; // 137 chars max + null terminator
|
|
static int composePos = 0;
|
|
static uint8_t composeChannelIdx = 0; // Which channel to send to
|
|
static unsigned long lastComposeRefresh = 0;
|
|
static bool composeNeedsRefresh = false;
|
|
#define COMPOSE_REFRESH_INTERVAL 600 // ms between e-ink refreshes while typing (refresh takes ~650ms)
|
|
|
|
void initKeyboard();
|
|
void handleKeyboardInput();
|
|
void drawComposeScreen();
|
|
void sendComposedMessage();
|
|
#endif
|
|
|
|
// Believe it or not, this std C function is busted on some platforms!
|
|
static uint32_t _atoi(const char* sp) {
|
|
uint32_t n = 0;
|
|
while (*sp && *sp >= '0' && *sp <= '9') {
|
|
n *= 10;
|
|
n += (*sp++ - '0');
|
|
}
|
|
return n;
|
|
}
|
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
#include <InternalFileSystem.h>
|
|
#if defined(QSPIFLASH)
|
|
#include <CustomLFS_QSPIFlash.h>
|
|
DataStore store(InternalFS, QSPIFlash, rtc_clock);
|
|
#else
|
|
#if defined(EXTRAFS)
|
|
#include <CustomLFS.h>
|
|
CustomLFS ExtraFS(0xD4000, 0x19000, 128);
|
|
DataStore store(InternalFS, ExtraFS, rtc_clock);
|
|
#else
|
|
DataStore store(InternalFS, rtc_clock);
|
|
#endif
|
|
#endif
|
|
#elif defined(RP2040_PLATFORM)
|
|
#include <LittleFS.h>
|
|
DataStore store(LittleFS, rtc_clock);
|
|
#elif defined(ESP32)
|
|
#include <SPIFFS.h>
|
|
DataStore store(SPIFFS, rtc_clock);
|
|
#endif
|
|
|
|
#ifdef ESP32
|
|
#ifdef WIFI_SSID
|
|
#include <helpers/esp32/SerialWifiInterface.h>
|
|
SerialWifiInterface serial_interface;
|
|
#ifndef TCP_PORT
|
|
#define TCP_PORT 5000
|
|
#endif
|
|
#elif defined(BLE_PIN_CODE)
|
|
#include <helpers/esp32/SerialBLEInterface.h>
|
|
SerialBLEInterface serial_interface;
|
|
#elif defined(SERIAL_RX)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
HardwareSerial companion_serial(1);
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(RP2040_PLATFORM)
|
|
//#ifdef WIFI_SSID
|
|
// #include <helpers/rp2040/SerialWifiInterface.h>
|
|
// SerialWifiInterface serial_interface;
|
|
// #ifndef TCP_PORT
|
|
// #define TCP_PORT 5000
|
|
// #endif
|
|
// #elif defined(BLE_PIN_CODE)
|
|
// #include <helpers/rp2040/SerialBLEInterface.h>
|
|
// SerialBLEInterface serial_interface;
|
|
#if defined(SERIAL_RX)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
HardwareSerial companion_serial(1);
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(NRF52_PLATFORM)
|
|
#ifdef BLE_PIN_CODE
|
|
#include <helpers/nrf52/SerialBLEInterface.h>
|
|
SerialBLEInterface serial_interface;
|
|
#else
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#endif
|
|
#elif defined(STM32_PLATFORM)
|
|
#include <helpers/ArduinoSerialInterface.h>
|
|
ArduinoSerialInterface serial_interface;
|
|
#else
|
|
#error "need to define a serial interface"
|
|
#endif
|
|
|
|
/* GLOBAL OBJECTS */
|
|
#ifdef DISPLAY_CLASS
|
|
#include "UITask.h"
|
|
UITask ui_task(&board, &serial_interface);
|
|
#endif
|
|
|
|
StdRNG fast_rng;
|
|
SimpleMeshTables tables;
|
|
MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
|
|
#ifdef DISPLAY_CLASS
|
|
, &ui_task
|
|
#endif
|
|
);
|
|
|
|
/* END GLOBAL OBJECTS */
|
|
|
|
void halt() {
|
|
while (1) ;
|
|
}
|
|
|
|
void setup() {
|
|
Serial.begin(115200);
|
|
delay(100); // Give serial time to initialize
|
|
MESH_DEBUG_PRINTLN("=== setup() - STARTING ===");
|
|
|
|
board.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - board.begin() done");
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
DisplayDriver* disp = NULL;
|
|
MESH_DEBUG_PRINTLN("setup() - about to call display.begin()");
|
|
|
|
// =========================================================================
|
|
// T-Deck Pro V1.1: Initialize E-Ink reset pin BEFORE display.begin()
|
|
// This is critical - the display won't work without proper reset sequence
|
|
// =========================================================================
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
// Initialize E-Ink reset pin (GPIO 16)
|
|
pinMode(PIN_DISPLAY_RST, OUTPUT);
|
|
digitalWrite(PIN_DISPLAY_RST, HIGH);
|
|
MESH_DEBUG_PRINTLN("setup() - E-Ink reset pin initialized");
|
|
|
|
// Initialize Touch reset pin (GPIO 38)
|
|
#ifdef CST328_PIN_RST
|
|
pinMode(CST328_PIN_RST, OUTPUT);
|
|
digitalWrite(CST328_PIN_RST, HIGH);
|
|
delay(20);
|
|
digitalWrite(CST328_PIN_RST, LOW);
|
|
delay(80);
|
|
digitalWrite(CST328_PIN_RST, HIGH);
|
|
delay(20);
|
|
MESH_DEBUG_PRINTLN("setup() - Touch reset pin initialized");
|
|
#endif
|
|
#endif
|
|
// =========================================================================
|
|
|
|
if (display.begin()) {
|
|
MESH_DEBUG_PRINTLN("setup() - display.begin() returned true");
|
|
disp = &display;
|
|
disp->startFrame();
|
|
#ifdef ST7789
|
|
disp->setTextSize(2);
|
|
#endif
|
|
disp->drawTextCentered(disp->width() / 2, 28, "Loading...");
|
|
disp->endFrame();
|
|
MESH_DEBUG_PRINTLN("setup() - Loading screen drawn");
|
|
} else {
|
|
MESH_DEBUG_PRINTLN("setup() - display.begin() returned false!");
|
|
}
|
|
#endif
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call radio_init()");
|
|
if (!radio_init()) {
|
|
MESH_DEBUG_PRINTLN("setup() - radio_init() FAILED! Halting.");
|
|
halt();
|
|
}
|
|
MESH_DEBUG_PRINTLN("setup() - radio_init() done");
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call fast_rng.begin()");
|
|
fast_rng.begin(radio_get_rng_seed());
|
|
MESH_DEBUG_PRINTLN("setup() - fast_rng.begin() done");
|
|
|
|
#if defined(NRF52_PLATFORM) || defined(STM32_PLATFORM)
|
|
MESH_DEBUG_PRINTLN("setup() - NRF52/STM32 filesystem init");
|
|
InternalFS.begin();
|
|
#if defined(QSPIFLASH)
|
|
if (!QSPIFlash.begin()) {
|
|
MESH_DEBUG_PRINTLN("CustomLFS_QSPIFlash: failed to initialize");
|
|
} else {
|
|
MESH_DEBUG_PRINTLN("CustomLFS_QSPIFlash: initialized successfully");
|
|
}
|
|
#else
|
|
#if defined(EXTRAFS)
|
|
ExtraFS.begin();
|
|
#endif
|
|
#endif
|
|
MESH_DEBUG_PRINTLN("setup() - about to call store.begin()");
|
|
store.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - store.begin() done");
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.begin()");
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.begin() done");
|
|
|
|
#ifdef BLE_PIN_CODE
|
|
MESH_DEBUG_PRINTLN("setup() - about to call serial_interface.begin() with BLE");
|
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
|
MESH_DEBUG_PRINTLN("setup() - serial_interface.begin() done");
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.startInterface()");
|
|
the_mesh.startInterface(serial_interface);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.startInterface() done");
|
|
|
|
#elif defined(RP2040_PLATFORM)
|
|
MESH_DEBUG_PRINTLN("setup() - RP2040 filesystem init");
|
|
LittleFS.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - about to call store.begin()");
|
|
store.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - store.begin() done");
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.begin()");
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.begin() done");
|
|
|
|
//#ifdef WIFI_SSID
|
|
// WiFi.begin(WIFI_SSID, WIFI_PWD);
|
|
// serial_interface.begin(TCP_PORT);
|
|
// #elif defined(BLE_PIN_CODE)
|
|
// char dev_name[32+16];
|
|
// sprintf(dev_name, "%s%s", BLE_NAME_PREFIX, the_mesh.getNodeName());
|
|
// serial_interface.begin(dev_name, the_mesh.getBLEPin());
|
|
#if defined(SERIAL_RX)
|
|
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
|
companion_serial.begin(115200);
|
|
serial_interface.begin(companion_serial);
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.startInterface()");
|
|
the_mesh.startInterface(serial_interface);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.startInterface() done");
|
|
|
|
#elif defined(ESP32)
|
|
MESH_DEBUG_PRINTLN("setup() - ESP32 filesystem init - calling SPIFFS.begin()");
|
|
SPIFFS.begin(true);
|
|
MESH_DEBUG_PRINTLN("setup() - SPIFFS.begin() done");
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call store.begin()");
|
|
store.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - store.begin() done");
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.begin()");
|
|
the_mesh.begin(
|
|
#ifdef DISPLAY_CLASS
|
|
disp != NULL
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.begin() done");
|
|
|
|
#ifdef WIFI_SSID
|
|
MESH_DEBUG_PRINTLN("setup() - WiFi mode");
|
|
WiFi.begin(WIFI_SSID, WIFI_PWD);
|
|
serial_interface.begin(TCP_PORT);
|
|
#elif defined(BLE_PIN_CODE)
|
|
MESH_DEBUG_PRINTLN("setup() - about to call serial_interface.begin() with BLE");
|
|
serial_interface.begin(BLE_NAME_PREFIX, the_mesh.getNodePrefs()->node_name, the_mesh.getBLEPin());
|
|
MESH_DEBUG_PRINTLN("setup() - serial_interface.begin() done");
|
|
#elif defined(SERIAL_RX)
|
|
companion_serial.setPins(SERIAL_RX, SERIAL_TX);
|
|
companion_serial.begin(115200);
|
|
serial_interface.begin(companion_serial);
|
|
#else
|
|
serial_interface.begin(Serial);
|
|
#endif
|
|
MESH_DEBUG_PRINTLN("setup() - about to call the_mesh.startInterface()");
|
|
the_mesh.startInterface(serial_interface);
|
|
MESH_DEBUG_PRINTLN("setup() - the_mesh.startInterface() done");
|
|
|
|
#else
|
|
#error "need to define filesystem"
|
|
#endif
|
|
|
|
MESH_DEBUG_PRINTLN("setup() - about to call sensors.begin()");
|
|
sensors.begin();
|
|
MESH_DEBUG_PRINTLN("setup() - sensors.begin() done");
|
|
|
|
// IMPORTANT: sensors.begin() calls initBasicGPS() which steals the GPS pins for Serial1
|
|
// We need to reinitialize Serial2 to reclaim them
|
|
#if HAS_GPS
|
|
Serial2.end(); // Close any existing Serial2
|
|
Serial2.begin(GPS_BAUDRATE, SERIAL_8N1, GPS_RX_PIN, GPS_TX_PIN);
|
|
MESH_DEBUG_PRINTLN("setup() - Reinitialized Serial2 for GPS after sensors.begin()");
|
|
#endif
|
|
|
|
#ifdef DISPLAY_CLASS
|
|
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");
|
|
#endif
|
|
|
|
// Initialize T-Deck Pro keyboard
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
initKeyboard();
|
|
#endif
|
|
|
|
// Enable GPS by default on T-Deck Pro
|
|
#if HAS_GPS
|
|
// Set GPS enabled in both sensor manager and node prefs
|
|
sensors.setSettingValue("gps", "1");
|
|
the_mesh.getNodePrefs()->gps_enabled = 1;
|
|
the_mesh.savePrefs();
|
|
MESH_DEBUG_PRINTLN("setup() - GPS enabled by default");
|
|
#endif
|
|
|
|
MESH_DEBUG_PRINTLN("=== setup() - COMPLETE ===");
|
|
}
|
|
|
|
void loop() {
|
|
the_mesh.loop();
|
|
sensors.loop();
|
|
#ifdef DISPLAY_CLASS
|
|
// Skip UITask rendering when in compose mode to prevent flickering
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
if (!composeMode) {
|
|
ui_task.loop();
|
|
} else {
|
|
// Handle debounced compose screen refresh
|
|
if (composeNeedsRefresh && (millis() - lastComposeRefresh) >= COMPOSE_REFRESH_INTERVAL) {
|
|
drawComposeScreen();
|
|
lastComposeRefresh = millis();
|
|
composeNeedsRefresh = false;
|
|
}
|
|
}
|
|
#else
|
|
ui_task.loop();
|
|
#endif
|
|
#endif
|
|
rtc_clock.tick();
|
|
|
|
// Handle T-Deck Pro keyboard input
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
handleKeyboardInput();
|
|
#endif
|
|
}
|
|
|
|
// ============================================================================
|
|
// T-DECK PRO KEYBOARD FUNCTIONS
|
|
// ============================================================================
|
|
|
|
#if defined(LilyGo_TDeck_Pro)
|
|
|
|
void initKeyboard() {
|
|
// Keyboard uses the same I2C bus as other peripherals (already initialized)
|
|
if (keyboard.begin()) {
|
|
MESH_DEBUG_PRINTLN("setup() - Keyboard initialized");
|
|
composeBuffer[0] = '\0';
|
|
composePos = 0;
|
|
composeMode = false;
|
|
composeNeedsRefresh = false;
|
|
lastComposeRefresh = 0;
|
|
} else {
|
|
MESH_DEBUG_PRINTLN("setup() - Keyboard initialization failed!");
|
|
}
|
|
}
|
|
|
|
void handleKeyboardInput() {
|
|
if (!keyboard.isReady()) return;
|
|
|
|
char key = keyboard.readKey();
|
|
if (key == 0) return;
|
|
|
|
Serial.printf("handleKeyboardInput: key='%c' (0x%02X) composeMode=%d\n",
|
|
key >= 32 ? key : '?', key, composeMode);
|
|
|
|
if (composeMode) {
|
|
// In compose mode - handle text input
|
|
if (key == '\r') {
|
|
// Enter - send the message
|
|
Serial.println("Compose: Enter pressed, sending...");
|
|
if (composePos > 0) {
|
|
sendComposedMessage();
|
|
}
|
|
composeMode = false;
|
|
composeBuffer[0] = '\0';
|
|
composePos = 0;
|
|
ui_task.gotoHomeScreen();
|
|
return;
|
|
}
|
|
|
|
if (key == '\b') {
|
|
// Backspace - check if shift was recently pressed for cancel combo
|
|
if (keyboard.wasShiftRecentlyPressed(500)) {
|
|
// Shift+Backspace = Cancel (works anytime)
|
|
Serial.println("Compose: Shift+Backspace, cancelling...");
|
|
composeMode = false;
|
|
composeBuffer[0] = '\0';
|
|
composePos = 0;
|
|
ui_task.gotoHomeScreen();
|
|
return;
|
|
}
|
|
// Regular backspace - delete last character
|
|
if (composePos > 0) {
|
|
composePos--;
|
|
composeBuffer[composePos] = '\0';
|
|
Serial.printf("Compose: Backspace, pos now %d\n", composePos);
|
|
composeNeedsRefresh = true; // Use debounced refresh
|
|
}
|
|
return;
|
|
}
|
|
|
|
// A/D keys switch channels (only when buffer is empty or as special function)
|
|
if ((key == 'a' || key == 'A') && composePos == 0) {
|
|
// Previous channel
|
|
if (composeChannelIdx > 0) {
|
|
composeChannelIdx--;
|
|
} else {
|
|
// Wrap to last valid channel
|
|
for (uint8_t i = MAX_GROUP_CHANNELS - 1; i > 0; i--) {
|
|
ChannelDetails ch;
|
|
if (the_mesh.getChannel(i, ch) && ch.name[0] != '\0') {
|
|
composeChannelIdx = i;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
Serial.printf("Compose: Channel switched to %d\n", composeChannelIdx);
|
|
drawComposeScreen();
|
|
return;
|
|
}
|
|
|
|
if ((key == 'd' || key == 'D') && composePos == 0) {
|
|
// Next channel
|
|
ChannelDetails ch;
|
|
uint8_t nextIdx = composeChannelIdx + 1;
|
|
if (the_mesh.getChannel(nextIdx, ch) && ch.name[0] != '\0') {
|
|
composeChannelIdx = nextIdx;
|
|
} else {
|
|
composeChannelIdx = 0; // Wrap to first channel
|
|
}
|
|
Serial.printf("Compose: Channel switched to %d\n", composeChannelIdx);
|
|
drawComposeScreen();
|
|
return;
|
|
}
|
|
|
|
// Regular character input
|
|
if (key >= 32 && key < 127 && composePos < 137) {
|
|
composeBuffer[composePos++] = key;
|
|
composeBuffer[composePos] = '\0';
|
|
Serial.printf("Compose: Added '%c', pos now %d\n", key, composePos);
|
|
composeNeedsRefresh = true; // Use debounced refresh
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Normal mode - not composing
|
|
switch (key) {
|
|
case 'c':
|
|
case 'C':
|
|
// Enter compose mode
|
|
composeMode = true;
|
|
composeBuffer[0] = '\0';
|
|
composePos = 0;
|
|
// If on channel screen, sync compose channel with viewed channel
|
|
if (ui_task.isOnChannelScreen()) {
|
|
composeChannelIdx = ui_task.getChannelScreenViewIdx();
|
|
}
|
|
Serial.printf("Entering compose mode, channel %d\n", composeChannelIdx);
|
|
drawComposeScreen();
|
|
break;
|
|
|
|
case 'm':
|
|
case 'M':
|
|
// Go to channel message screen
|
|
Serial.println("Opening channel messages");
|
|
ui_task.gotoChannelScreen();
|
|
break;
|
|
|
|
case 'w':
|
|
case 'W':
|
|
// Navigate up/previous (scroll on channel screen)
|
|
if (ui_task.isOnChannelScreen()) {
|
|
ui_task.injectKey('w'); // Pass directly for channel switching
|
|
} else {
|
|
Serial.println("Nav: Previous");
|
|
ui_task.injectKey(0xF2); // KEY_PREV
|
|
}
|
|
break;
|
|
|
|
case 's':
|
|
case 'S':
|
|
// Navigate down/next (scroll on channel screen)
|
|
if (ui_task.isOnChannelScreen()) {
|
|
ui_task.injectKey('s'); // Pass directly for channel switching
|
|
} else {
|
|
Serial.println("Nav: Next");
|
|
ui_task.injectKey(0xF1); // KEY_NEXT
|
|
}
|
|
break;
|
|
|
|
case 'a':
|
|
case 'A':
|
|
// Navigate left or switch channel (on channel screen)
|
|
if (ui_task.isOnChannelScreen()) {
|
|
ui_task.injectKey('a'); // Pass directly for channel switching
|
|
} else {
|
|
Serial.println("Nav: Previous");
|
|
ui_task.injectKey(0xF2); // KEY_PREV
|
|
}
|
|
break;
|
|
|
|
case 'd':
|
|
case 'D':
|
|
// Navigate right or switch channel (on channel screen)
|
|
if (ui_task.isOnChannelScreen()) {
|
|
ui_task.injectKey('d'); // Pass directly for channel switching
|
|
} else {
|
|
Serial.println("Nav: Next");
|
|
ui_task.injectKey(0xF1); // KEY_NEXT
|
|
}
|
|
break;
|
|
|
|
case '\r':
|
|
// Select/Enter
|
|
Serial.println("Nav: Enter/Select");
|
|
ui_task.injectKey(13); // KEY_ENTER
|
|
break;
|
|
|
|
case 'q':
|
|
case 'Q':
|
|
case '\b':
|
|
// Go back to home screen
|
|
Serial.println("Nav: Back to home");
|
|
ui_task.gotoHomeScreen();
|
|
break;
|
|
|
|
case ' ':
|
|
// Space - also acts as next/select
|
|
Serial.println("Nav: Space (Next)");
|
|
ui_task.injectKey(0xF1); // KEY_NEXT
|
|
break;
|
|
|
|
default:
|
|
Serial.printf("Unhandled key in normal mode: '%c' (0x%02X)\n", key, key);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void drawComposeScreen() {
|
|
#ifdef DISPLAY_CLASS
|
|
display.startFrame();
|
|
display.setTextSize(1);
|
|
display.setColor(DisplayDriver::GREEN);
|
|
display.setCursor(0, 0);
|
|
|
|
// Get the channel name for display
|
|
ChannelDetails channel;
|
|
char headerBuf[40];
|
|
if (the_mesh.getChannel(composeChannelIdx, channel)) {
|
|
snprintf(headerBuf, sizeof(headerBuf), "To: %s", channel.name);
|
|
} else {
|
|
snprintf(headerBuf, sizeof(headerBuf), "To: Channel %d", composeChannelIdx);
|
|
}
|
|
display.print(headerBuf);
|
|
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
display.drawRect(0, 11, display.width(), 1);
|
|
|
|
display.setCursor(0, 14);
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
|
|
// Word wrap the compose buffer - calculate chars per line based on actual font width
|
|
int x = 0;
|
|
int y = 14;
|
|
uint16_t testWidth = display.getTextWidth("MMMMMMMMMM"); // 10 wide chars
|
|
int charsPerLine = (testWidth > 0) ? (display.width() * 10) / testWidth : 20;
|
|
if (charsPerLine < 12) charsPerLine = 12;
|
|
if (charsPerLine > 40) charsPerLine = 40;
|
|
char charStr[2] = {0, 0}; // Buffer for single character as string
|
|
|
|
for (int i = 0; i < composePos; i++) {
|
|
charStr[0] = composeBuffer[i];
|
|
display.print(charStr);
|
|
x++;
|
|
if (x >= charsPerLine) {
|
|
x = 0;
|
|
y += 11;
|
|
display.setCursor(0, y);
|
|
}
|
|
}
|
|
|
|
// Show cursor
|
|
display.print("_");
|
|
|
|
// Status bar
|
|
int statusY = display.height() - 12;
|
|
display.setColor(DisplayDriver::LIGHT);
|
|
display.drawRect(0, statusY - 2, display.width(), 1);
|
|
display.setCursor(0, statusY);
|
|
display.setColor(DisplayDriver::YELLOW);
|
|
|
|
char status[40];
|
|
if (composePos == 0) {
|
|
// Empty buffer - show channel switching hint
|
|
display.print("A/D:Ch");
|
|
sprintf(status, "Sh+Del:X");
|
|
display.setCursor(display.width() - display.getTextWidth(status) - 2, statusY);
|
|
display.print(status);
|
|
} else {
|
|
// Has text - show send/cancel hint
|
|
sprintf(status, "%d/137 Ent:Send", composePos);
|
|
display.print(status);
|
|
sprintf(status, "Sh+Del:X");
|
|
display.setCursor(display.width() - display.getTextWidth(status) - 2, statusY);
|
|
display.print(status);
|
|
}
|
|
|
|
display.endFrame();
|
|
#endif
|
|
}
|
|
|
|
void sendComposedMessage() {
|
|
if (composePos == 0) return;
|
|
|
|
// Get the selected channel
|
|
ChannelDetails channel;
|
|
if (the_mesh.getChannel(composeChannelIdx, channel)) {
|
|
uint32_t timestamp = rtc_clock.getCurrentTime();
|
|
|
|
// Send to channel
|
|
if (the_mesh.sendGroupMessage(timestamp, channel.channel,
|
|
the_mesh.getNodePrefs()->node_name,
|
|
composeBuffer, composePos)) {
|
|
// Add the sent message to local channel history so we can see what we sent
|
|
ui_task.addSentChannelMessage(composeChannelIdx,
|
|
the_mesh.getNodePrefs()->node_name,
|
|
composeBuffer);
|
|
|
|
// Queue message for BLE app sync (so sent messages appear in companion app)
|
|
the_mesh.queueSentChannelMessage(composeChannelIdx, timestamp,
|
|
the_mesh.getNodePrefs()->node_name,
|
|
composeBuffer);
|
|
|
|
ui_task.showAlert("Sent!", 1500);
|
|
} else {
|
|
ui_task.showAlert("Send failed!", 1500);
|
|
}
|
|
} else {
|
|
ui_task.showAlert("No channel!", 1500);
|
|
}
|
|
}
|
|
|
|
#endif // LilyGo_TDeck_Pro
|