Compare commits

...

2 Commits

Author SHA1 Message Date
Jonathan Bennett
75e41c1604 Only init screen if one found 2025-06-07 14:07:48 -05:00
Jonathan Bennett
3955a5616e remove legacy string->print() 2025-06-07 10:46:03 -05:00
9 changed files with 273 additions and 337 deletions

View File

@ -184,7 +184,6 @@ static void serialEnter()
setBluetoothEnable(false);
if (screen) {
screen->setOn(true);
screen->print("Serial connected\n");
}
}
@ -192,8 +191,6 @@ static void serialExit()
{
// Turn bluetooth back on when we leave serial stream API
setBluetoothEnable(true);
if (screen)
screen->print("Serial disconnected\n");
}
static void powerEnter()
@ -208,12 +205,6 @@ static void powerEnter()
screen->setOn(true);
setBluetoothEnable(true);
// within enter() the function getState() returns the state we came from
// Mothballed: print change of power-state to device screen
/* if (strcmp(powerFSM.getState()->name, "BOOT") != 0 && strcmp(powerFSM.getState()->name, "POWER") != 0 &&
strcmp(powerFSM.getState()->name, "DARK") != 0) {
screen->print("Powered...\n");
}*/
}
}
@ -231,10 +222,6 @@ static void powerExit()
if (screen)
screen->setOn(true);
setBluetoothEnable(true);
// Mothballed: print change of power-state to device screen
/*if (!isPowered())
screen->print("Unpowered...\n");*/
}
static void onEnter()

View File

@ -12,7 +12,6 @@ enum class Cmd {
STOP_ALERT_FRAME,
START_FIRMWARE_UPDATE_SCREEN,
STOP_BOOT_SCREEN,
PRINT,
SHOW_PREV_FRAME,
SHOW_NEXT_FRAME
};

View File

@ -1201,10 +1201,6 @@ int32_t Screen::runOnce()
EINK_ADD_FRAMEFLAG(dispdev, COSMETIC); // E-Ink: Explicitly use full-refresh for next frame
setFrames();
break;
case Cmd::PRINT:
handlePrint(cmd.print_text);
free(cmd.print_text);
break;
default:
LOG_ERROR("Invalid screen cmd");
}
@ -1334,15 +1330,6 @@ void Screen::setFrames(FrameFocus focus)
showingNormalScreen = true;
indicatorIcons.clear();
#ifdef USE_EINK
// If user has disabled the screensaver, warn them after boot
static bool warnedScreensaverDisabled = false;
if (config.display.screen_on_secs == 0 && !warnedScreensaverDisabled) {
screen->print("Screensaver disabled\n");
warnedScreensaverDisabled = true;
}
#endif
moduleFrames = MeshModule::GetMeshModulesWithUIFrames();
LOG_DEBUG("Show %d module frames", moduleFrames.size());
#ifdef DEBUG_PORT
@ -1627,17 +1614,6 @@ void Screen::removeFunctionSymbol(std::string sym)
setFastFramerate();
}
void Screen::handlePrint(const char *text)
{
// the string passed into us probably has a newline, but that would confuse the logging system
// so strip it
LOG_DEBUG("Screen: %.*s", strlen(text) - 1, text);
if (!useDisplay || !showingNormalScreen)
return;
dispdev->print(text);
}
void Screen::handleOnPress()
{
// If Canned Messages is using the "Scan and Select" input, dismiss the canned message frame when user button is pressed

View File

@ -30,7 +30,6 @@ class Screen
void onPress() {}
void setup() {}
void setOn(bool) {}
void print(const char *) {}
void doDeepSleep() {}
void forceDisplay(bool forceUiUpdate = false) {}
void startFirmwareUpdateScreen() {}
@ -320,20 +319,6 @@ class Screen : public concurrency::OSThread
/// Stops showing the boot screen.
void stopBootScreen() { enqueueCmd(ScreenCmd{.cmd = Cmd::STOP_BOOT_SCREEN}); }
/// Writes a string to the screen.
void print(const char *text)
{
ScreenCmd cmd;
cmd.cmd = Cmd::PRINT;
// TODO(girts): strdup() here is scary, but we can't use std::string as
// FreeRTOS queue is just dumbly copying memory contents. It would be
// nice if we had a queue that could copy objects by value.
cmd.print_text = strdup(text);
if (!enqueueCmd(cmd)) {
free(cmd.print_text);
}
}
/// Overrides the default utf8 character conversion, to replace empty space with question marks
static char customFontTableLookup(const uint8_t ch)
{
@ -614,7 +599,6 @@ class Screen : public concurrency::OSThread
void handleOnPress();
void handleShowNextFrame();
void handleShowPrevFrame();
void handlePrint(const char *text);
void handleStartFirmwareUpdateScreen();
// Info collected by setFrames method.

View File

@ -166,13 +166,6 @@ void ButtonThread::sendAdHocPosition()
{
service->refreshLocalMeshNode();
auto sentPosition = service->trySendPosition(NODENUM_BROADCAST, true);
if (screen) {
if (sentPosition)
screen->print("Sent ad-hoc position\n");
else
screen->print("Sent ad-hoc nodeinfo\n");
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
}
playComboTune();
}
@ -263,292 +256,294 @@ int32_t ButtonThread::runOnce()
#endif
if (btnEvent != BUTTON_EVENT_NONE) {
if (screen) {
#if HAS_SCREEN
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_WARN("press!");
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_WARN("press!");
// Play boop sound for every button press
playBoop();
// Play boop sound for every button press
playBoop();
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
// Start tracking for potential combination
waitingForLongPress = true;
shortPressTime = millis();
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_WARN("press!");
// Play boop sound for every button press
playBoop();
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_DOUBLE_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
waitingForLongPress = false;
break;
}
case BUTTON_EVENT_LONG_PRESSED: {
LOG_WARN("Long press!");
// Play beep sound
playBeep();
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_LONG_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
waitingForLongPress = false;
break;
}
default:
// Ignore all other events on screen devices
break;
}
btnEvent = BUTTON_EVENT_NONE;
#else
// On devices without screen: full legacy logic
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_BUTTON("press!");
// Play boop sound for every button press
playBoop();
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
#ifdef ELECROW_ThinkNode_M1
sendAdHocPosition();
break;
#endif
// Start tracking for potential combination
waitingForLongPress = true;
shortPressTime = millis();
switchPage();
break;
}
case BUTTON_EVENT_PRESSED_SCREEN: {
LOG_BUTTON("AltPress!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
#ifdef ELECROW_ThinkNode_M1
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
switchPage();
break;
#endif
// turn screen on or off
screen_flag = !screen_flag;
if (screen)
screen->setOn(screen_flag);
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
#ifdef ELECROW_ThinkNode_M1
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
#endif
// Send GPS position immediately
sendAdHocPosition();
// Show temporary on-screen confirmation banner for 3 seconds
screen->showOverlayBanner("Ad-hoc Ping Sent", 3000);
break;
}
case BUTTON_EVENT_MULTI_PRESSED: {
LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
switch (multipressClickCount) {
#if HAS_GPS && !defined(ELECROW_ThinkNode_M1)
// 3 clicks: toggle GPS
case 3:
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
const char *statusMsg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED)
? "GPS Enabled"
: "GPS Disabled";
if (screen) {
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
screen->showOverlayBanner(statusMsg, 3000);
}
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
// Start tracking for potential combination
waitingForLongPress = true;
shortPressTime = millis();
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_WARN("press!");
// Play boop sound for every button press
playBoop();
// Forward single press to InputBroker (but NOT as DOWN/SELECT, just forward a "button press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_DOUBLE_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
waitingForLongPress = false;
break;
}
case BUTTON_EVENT_LONG_PRESSED: {
LOG_WARN("Long press!");
// Play beep sound
playBeep();
// Forward long press to InputBroker (but NOT as DOWN/SELECT, just forward a "button long press" event)
if (inputBroker) {
InputEvent evt = {"button", (char)INPUT_BROKER_MSG_BUTTON_LONG_PRESSED, 0, 0, 0};
inputBroker->injectInputEvent(&evt);
}
waitingForLongPress = false;
break;
}
default:
// Ignore all other events on screen devices
break;
}
btnEvent = BUTTON_EVENT_NONE;
#endif
} else {
// On devices without screen: full legacy logic
switch (btnEvent) {
case BUTTON_EVENT_PRESSED: {
LOG_BUTTON("press!");
// Play boop sound for every button press
playBoop();
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
#ifdef ELECROW_ThinkNode_M1
sendAdHocPosition();
break;
#endif
// Start tracking for potential combination
waitingForLongPress = true;
shortPressTime = millis();
switchPage();
break;
}
case BUTTON_EVENT_PRESSED_SCREEN: {
LOG_BUTTON("AltPress!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
#ifdef ELECROW_ThinkNode_M1
// If a nag notification is running, stop it and prevent other actions
if (moduleConfig.external_notification.enabled && (externalNotificationModule->nagCycleCutoff != UINT32_MAX)) {
externalNotificationModule->stopNow();
break;
}
switchPage();
break;
#endif
// turn screen on or off
screen_flag = !screen_flag;
if (screen)
screen->setOn(screen_flag);
break;
}
case BUTTON_EVENT_DOUBLE_PRESSED: {
LOG_BUTTON("Double press!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
#ifdef ELECROW_ThinkNode_M1
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
#endif
// Send GPS position immediately
sendAdHocPosition();
// Show temporary on-screen confirmation banner for 3 seconds
screen->showOverlayBanner("Ad-hoc Ping Sent", 3000);
break;
}
case BUTTON_EVENT_MULTI_PRESSED: {
LOG_BUTTON("Mulitipress! %hux", multipressClickCount);
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
switch (multipressClickCount) {
#if HAS_GPS && !defined(ELECROW_ThinkNode_M1)
// 3 clicks: toggle GPS
case 3:
if (!config.device.disable_triple_click && (gps != nullptr)) {
gps->toggleGpsMode();
const char *statusMsg = (config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_ENABLED)
? "GPS Enabled"
: "GPS Disabled";
if (screen) {
screen->forceDisplay(true); // Force a new UI frame, then force an EInk update
screen->showOverlayBanner(statusMsg, 3000);
}
}
break;
#elif defined(ELECROW_ThinkNode_M1) || defined(ELECROW_ThinkNode_M2)
case 3:
LOG_INFO("3 clicks: toggle buzzer");
buzzer_flag = !buzzer_flag;
if (!buzzer_flag)
noTone(PIN_BUZZER);
break;
case 3:
LOG_INFO("3 clicks: toggle buzzer");
buzzer_flag = !buzzer_flag;
if (!buzzer_flag)
noTone(PIN_BUZZER);
break;
#endif
#if defined(USE_EINK) && defined(PIN_EINK_EN) && !defined(ELECROW_ThinkNode_M1) // i.e. T-Echo
// 4 clicks: toggle backlight
case 4:
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
// 4 clicks: toggle backlight
case 4:
digitalWrite(PIN_EINK_EN, digitalRead(PIN_EINK_EN) == LOW);
break;
#endif
#if !MESHTASTIC_EXCLUDE_SCREEN && HAS_SCREEN
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
case 5:
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
break;
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
case 6:
if (accelerometerThread) {
accelerometerThread->calibrate(60);
}
break;
// 5 clicks: start accelerometer/magenetometer calibration for 30 seconds
case 5:
if (accelerometerThread) {
accelerometerThread->calibrate(30);
}
break;
// 6 clicks: start accelerometer/magenetometer calibration for 60 seconds
case 6:
if (accelerometerThread) {
accelerometerThread->calibrate(60);
}
break;
#endif
// No valid multipress action
default:
// No valid multipress action
default:
break;
} // end switch: click count
break;
} // end switch: click count
} // end multipress event
break;
} // end multipress event
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!");
case BUTTON_EVENT_LONG_PRESSED: {
LOG_BUTTON("Long press!");
// Check if this is part of a short-press + long-press combination
if (waitingForLongPress && (millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) {
LOG_BUTTON("Combo detected: short-press + long-press!");
btnEvent = BUTTON_EVENT_COMBO_SHORT_LONG;
waitingForLongPress = false;
break;
}
// Check if this is part of a short-press + long-press combination
if (waitingForLongPress && (millis() - shortPressTime) <= BUTTON_COMBO_TIMEOUT_MS) {
LOG_BUTTON("Combo detected: short-press + long-press!");
btnEvent = BUTTON_EVENT_COMBO_SHORT_LONG;
// Reset combination tracking
waitingForLongPress = false;
break;
}
// Reset combination tracking
waitingForLongPress = false;
powerFSM.trigger(EVENT_PRESS);
if (screen) {
// Show shutdown message as a temporary overlay banner
screen->showOverlayBanner("Shutting Down..."); // Display for 3 seconds
}
// Lead-up sound already played during button hold
// Just a simple beep to confirm long press threshold reached
playBeep();
break;
}
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("Shutdown from long press");
// Reset combination tracking
waitingForLongPress = false;
playShutdownMelody();
delay(3000);
power->shutdown();
nodeDB->saveToDisk();
break;
}
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
// Ignore if: no screen
if (!screen)
break;
#ifdef TTGO_T_ECHO
// Ignore if: TX in progress
// Uncommon T-Echo hardware bug, LoRa TX triggers touch button
if (!RadioLibInterface::instance || RadioLibInterface::instance->isSending())
break;
#endif
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
powerFSM.trigger(EVENT_PRESS);
// Update display (legacy behaviour)
screen->forceDisplay();
break;
}
if (screen) {
// Show shutdown message as a temporary overlay banner
screen->showOverlayBanner("Shutting Down..."); // Display for 3 seconds
}
// Lead-up sound already played during button hold
// Just a simple beep to confirm long press threshold reached
playBeep();
break;
}
// Do actual shutdown when button released, otherwise the button release
// may wake the board immediatedly.
case BUTTON_EVENT_LONG_RELEASED: {
LOG_INFO("Shutdown from long press");
// Reset combination tracking
waitingForLongPress = false;
playShutdownMelody();
delay(3000);
power->shutdown();
nodeDB->saveToDisk();
break;
}
#ifdef BUTTON_PIN_TOUCH
case BUTTON_EVENT_TOUCH_LONG_PRESSED: {
LOG_BUTTON("Touch press!");
// Play boop sound for every button press
playBoop();
// Reset combination tracking
waitingForLongPress = false;
// Ignore if: no screen
if (!screen)
break;
#ifdef TTGO_T_ECHO
// Ignore if: TX in progress
// Uncommon T-Echo hardware bug, LoRa TX triggers touch button
if (!RadioLibInterface::instance || RadioLibInterface::instance->isSending())
break;
#endif
// Wake if asleep
if (powerFSM.getState() == &stateDARK)
powerFSM.trigger(EVENT_PRESS);
// Update display (legacy behaviour)
screen->forceDisplay();
break;
}
#endif // BUTTON_PIN_TOUCH
case BUTTON_EVENT_COMBO_SHORT_LONG: {
// Placeholder for short-press + long-press combination
LOG_BUTTON("Short-press + Long-press combination detected!");
case BUTTON_EVENT_COMBO_SHORT_LONG: {
// Placeholder for short-press + long-press combination
LOG_BUTTON("Short-press + Long-press combination detected!");
// Play the combination tune
playComboTune();
// Play the combination tune
playComboTune();
// Optionally show a message on screen
if (screen) {
screen->showOverlayBanner("Combo Tune Played", 2000);
// Optionally show a message on screen
if (screen) {
screen->showOverlayBanner("Combo Tune Played", 2000);
}
break;
}
break;
}
default:
break;
default:
break;
}
btnEvent = BUTTON_EVENT_NONE;
}
btnEvent = BUTTON_EVENT_NONE;
#endif
}
return 50;

View File

@ -850,9 +850,21 @@ void setup()
// Initialize the screen first so we can show the logo while we start up everything else.
#if HAS_SCREEN
if (config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
#if defined(ST7701_CS) || defined(ST7735_CS) || defined(USE_EINK) || defined(ILI9341_DRIVER) || defined(ILI9342_DRIVER) || \
defined(ST7789_CS) || defined(HX8357_CS) || defined(USE_ST7789) || defined(ILI9488_CS)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
}
#elif defined(ARCH_PORTDUINO)
if ((screen_found.port != ScanI2C::I2CPort::NO_I2C || settingsMap[displayPanel]) &&
config.display.displaymode != meshtastic_Config_DisplayConfig_DisplayMode_COLOR) {
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
}
#else
if (screen_found.port != ScanI2C::I2CPort::NO_I2C)
screen = new graphics::Screen(screen_found, screen_model, screen_geometry);
#endif
}
#endif // HAS_SCREEN
// setup TZ prior to time actions.
#if !MESHTASTIC_EXCLUDE_TZ
LOG_DEBUG("Use compiled/slipstreamed %s", slipstreamTZString); // important, removing this clobbers our magic string
@ -953,9 +965,6 @@ void setup()
screen->setup();
#endif
#endif
if (screen) {
screen->print("Started...\n");
}
#ifdef PIN_PWR_DELAY_MS
// This may be required to give the peripherals time to power up.

View File

@ -1804,10 +1804,6 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
/// Record an error that should be reported via analytics
void recordCriticalError(meshtastic_CriticalErrorCode code, uint32_t address, const char *filename)
{
// Print error to screen and serial port
String lcd = String("Critical error ") + code + "!\n";
if (screen)
screen->print(lcd.c_str());
if (filename) {
LOG_ERROR("NOTE! Record critical error %d at %s:%lu", code, filename, address);
} else {

View File

@ -83,10 +83,6 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r
switch (p.type) {
case meshtastic_HardwareMessage_Type_WRITE_GPIOS: {
// Print notification to LCD screen
if (screen)
screen->print("Write GPIOs\n");
pinModes(p.gpio_mask, OUTPUT, availablePins);
for (uint8_t i = 0; i < NUM_GPIOS; i++) {
uint64_t mask = 1ULL << i;
@ -99,10 +95,6 @@ bool RemoteHardwareModule::handleReceivedProtobuf(const meshtastic_MeshPacket &r
}
case meshtastic_HardwareMessage_Type_READ_GPIOS: {
// Print notification to LCD screen
if (screen)
screen->print("Read GPIOs\n");
uint64_t res = digitalReads(p.gpio_mask, availablePins);
// Send the reply

View File

@ -14,8 +14,6 @@ meshtastic_MeshPacket *ReplyModule::allocReply()
// The incoming message is in p.payload
LOG_INFO("Received message from=0x%0x, id=%d, msg=%.*s", req.from, req.id, p.payload.size, p.payload.bytes);
#endif
if (screen)
screen->print("Send reply\n");
const char *replyStr = "Message Received";
auto reply = allocDataPacket(); // Allocate a packet for sending