From 8cdb34a2996e1727b14c215266ff2f5def057f99 Mon Sep 17 00:00:00 2001 From: Ben Meadors Date: Sun, 1 Jun 2025 15:54:54 -0500 Subject: [PATCH] Get rid of Arduino Strings --- src/graphics/Screen.cpp | 86 +++++++++++-------- src/graphics/Screen.h | 6 +- src/graphics/draw/DebugRenderer.cpp | 34 +++++--- src/graphics/draw/NodeListRenderer.cpp | 21 ++--- src/graphics/draw/NodeListRenderer.h | 2 +- src/graphics/draw/NotificationRenderer.cpp | 42 +++++---- src/graphics/draw/UIRenderer.cpp | 44 +++++----- src/modules/CannedMessageModule.cpp | 45 +++++----- src/modules/SerialModule.cpp | 70 +++++++++------ src/modules/Telemetry/HealthTelemetry.cpp | 25 ++++-- src/modules/Telemetry/PowerTelemetry.cpp | 8 +- src/modules/Telemetry/Sensor/BME680Sensor.cpp | 10 +-- src/modules/Telemetry/Sensor/BME680Sensor.h | 2 +- src/nimble/NimbleBluetooth.cpp | 8 +- src/platform/esp32/WiFiOTA.cpp | 8 +- src/platform/esp32/WiFiOTA.h | 2 +- 16 files changed, 237 insertions(+), 176 deletions(-) diff --git a/src/graphics/Screen.cpp b/src/graphics/Screen.cpp index 474a000d5..be6d22a27 100644 --- a/src/graphics/Screen.cpp +++ b/src/graphics/Screen.cpp @@ -80,9 +80,6 @@ using graphics::numEmotes; using namespace meshtastic; /** @todo remove */ -String alertBannerMessage = ""; -uint32_t alertBannerUntil = 0; - namespace graphics { @@ -97,8 +94,12 @@ namespace graphics // A text message frame + debug frame + all the node infos FrameCallback *normalFrames; static uint32_t targetFramerate = IDLE_FRAMERATE; -static String alertBannerMessage; -static uint32_t alertBannerUntil = 0; +// Global variables for alert banner - explicitly define with extern "C" linkage to prevent optimization +extern "C" { +static char alertBannerBuffer[256] = ""; +char *alertBannerMessage = alertBannerBuffer; +uint32_t alertBannerUntil = 0; +} uint32_t logo_timeout = 5000; // 4 seconds for EACH logo @@ -146,10 +147,11 @@ extern bool hasUnreadMessage; // The banner appears in the center of the screen and disappears after the specified duration // Called to trigger a banner with custom message and duration -void Screen::showOverlayBanner(const String &message, uint32_t durationMs) +void Screen::showOverlayBanner(const char *message, uint32_t durationMs) { // Store the message and set the expiration timestamp - alertBannerMessage = message; + strncpy(alertBannerMessage, message, 255); + alertBannerMessage[255] = '\0'; // Ensure null termination alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs; } @@ -225,7 +227,8 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta UIRenderer::drawBattery(display, x, y + 7, imgBattery, powerStatus); if (powerStatus->getHasBattery()) { - String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; + char batteryPercent[8]; + snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent()); display->setFont(FONT_SMALL); @@ -255,16 +258,13 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta hour = 12; } - // hours string - String hourString = String(hour); + // Format time string + char timeString[16]; + snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute); - // minutes string - String minuteString = minute < 10 ? "0" + String(minute) : String(minute); - - String timeString = hourString + ":" + minuteString; - - // seconds string - String secondString = second < 10 ? "0" + String(second) : String(second); + // Format seconds string + char secondString[8]; + snprintf(secondString, sizeof(secondString), "%02d", second); float scale = 1.5; @@ -272,12 +272,12 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta uint16_t segmentHeight = SEGMENT_HEIGHT * scale; // calculate hours:minutes string width - uint16_t timeStringWidth = timeString.length() * 5; + uint16_t timeStringWidth = strlen(timeString) * 5; - for (uint8_t i = 0; i < timeString.length(); i++) { - String character = String(timeString[i]); + for (uint8_t i = 0; i < strlen(timeString); i++) { + char character = timeString[i]; - if (character == ":") { + if (character == ':') { timeStringWidth += segmentHeight; } else { timeStringWidth += segmentWidth + (segmentHeight * 2) + 4; @@ -285,7 +285,7 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta } // calculate seconds string width - uint16_t secondStringWidth = (secondString.length() * 12) + 4; + uint16_t secondStringWidth = (strlen(secondString) * 12) + 4; // sum these to get total string width uint16_t totalWidth = timeStringWidth + secondStringWidth; @@ -297,15 +297,15 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2); // iterate over characters in hours:minutes string and draw segmented characters - for (uint8_t i = 0; i < timeString.length(); i++) { - String character = String(timeString[i]); + for (uint8_t i = 0; i < strlen(timeString); i++) { + char character = timeString[i]; - if (character == ":") { + if (character == ':') { drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); hourMinuteTextX += segmentHeight + 6; } else { - drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale); + drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale); hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4; } @@ -457,7 +457,8 @@ void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *stat UIRenderer::drawBattery(display, x, y + 7, imgBattery, powerStatus); if (powerStatus->getHasBattery()) { - String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; + char batteryPercent[8]; + snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent()); display->setFont(FONT_SMALL); @@ -1711,21 +1712,30 @@ int Screen::handleTextMessage(const meshtastic_MeshPacket *packet) const char *longName = (node && node->has_user) ? node->user.long_name : nullptr; const char *msgRaw = reinterpret_cast(packet->decoded.payload.bytes); - String msg = String(msgRaw); - msg.trim(); // Remove leading/trailing whitespace/newlines - String banner; + char banner[256]; - // Match bell character or exact alert text - if (msg.indexOf("\x07") != -1) { - banner = "Alert Received"; - } else { - banner = "New Message"; + // Check for bell character in message to determine alert type + bool isAlert = false; + for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) { + if (msgRaw[i] == '\x07') { + isAlert = true; + break; + } } - if (longName && longName[0]) { - banner += "\nfrom "; - banner += longName; + if (isAlert) { + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "Alert Received\nfrom %s", longName); + } else { + strcpy(banner, "Alert Received"); + } + } else { + if (longName && longName[0]) { + snprintf(banner, sizeof(banner), "New Message\nfrom %s", longName); + } else { + strcpy(banner, "New Message"); + } } screen->showOverlayBanner(banner, 3000); diff --git a/src/graphics/Screen.h b/src/graphics/Screen.h index 0d791e463..5f74f12aa 100644 --- a/src/graphics/Screen.h +++ b/src/graphics/Screen.h @@ -279,7 +279,7 @@ class Screen : public concurrency::OSThread enqueueCmd(cmd); } - void showOverlayBanner(const String &message, uint32_t durationMs = 3000); + void showOverlayBanner(const char *message, uint32_t durationMs = 3000); void startFirmwareUpdateScreen() { @@ -698,8 +698,10 @@ class Screen : public concurrency::OSThread } // namespace graphics -extern String alertBannerMessage; +extern "C" { +extern char *alertBannerMessage; extern uint32_t alertBannerUntil; +} // Extern declarations for function symbols used in UIRenderer extern std::vector functionSymbol; diff --git a/src/graphics/draw/DebugRenderer.cpp b/src/graphics/draw/DebugRenderer.cpp index 00aabedfb..54fba7618 100644 --- a/src/graphics/draw/DebugRenderer.cpp +++ b/src/graphics/draw/DebugRenderer.cpp @@ -181,19 +181,19 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i } if (WiFi.status() != WL_CONNECTED) { - display->drawString(x, y, String("WiFi: Not Connected")); + display->drawString(x, y, "WiFi: Not Connected"); if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Not Connected")); + display->drawString(x + 1, y, "WiFi: Not Connected"); } else { - display->drawString(x, y, String("WiFi: Connected")); + display->drawString(x, y, "WiFi: Connected"); if (config.display.heading_bold) - display->drawString(x + 1, y, String("WiFi: Connected")); + display->drawString(x + 1, y, "WiFi: Connected"); - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())), y, - "RSSI " + String(WiFi.RSSI())); + char rssiStr[32]; + snprintf(rssiStr, sizeof(rssiStr), "RSSI %d", WiFi.RSSI()); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(rssiStr), y, rssiStr); if (config.display.heading_bold) { - display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, - "RSSI " + String(WiFi.RSSI())); + display->drawString(x + SCREEN_WIDTH - display->getStringWidth(rssiStr) - 1, y, rssiStr); } } @@ -212,7 +212,9 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i */ if (WiFi.status() == WL_CONNECTED) { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "IP: " + String(WiFi.localIP().toString().c_str())); + char ipStr[64]; + snprintf(ipStr, sizeof(ipStr), "IP: %s", WiFi.localIP().toString().c_str()); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, ipStr); } else if (WiFi.status() == WL_NO_SSID_AVAIL) { display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); } else if (WiFi.status() == WL_CONNECTION_LOST) { @@ -231,11 +233,15 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i } #else else { - display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "Unkown status: " + String(WiFi.status())); + char statusStr[32]; + snprintf(statusStr, sizeof(statusStr), "Unknown status: %d", WiFi.status()); + display->drawString(x, y + FONT_HEIGHT_SMALL * 1, statusStr); } #endif - display->drawString(x, y + FONT_HEIGHT_SMALL * 2, "SSID: " + String(wifiName)); + char ssidStr[64]; + snprintf(ssidStr, sizeof(ssidStr), "SSID: %s", wifiName); + display->drawString(x, y + FONT_HEIGHT_SMALL * 2, ssidStr); display->drawString(x, y + FONT_HEIGHT_SMALL * 3, "http://meshtastic.local"); @@ -274,9 +280,9 @@ void drawFrameSettings(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t display->drawString(x + 1, y, batStr); } else { // Line 1 - display->drawString(x, y, String("USB")); + display->drawString(x, y, "USB"); if (config.display.heading_bold) - display->drawString(x + 1, y, String("USB")); + display->drawString(x + 1, y, "USB"); } // auto mode = DisplayFormatters::getModemPresetDisplayName(config.lora.modem_preset, true); @@ -416,7 +422,7 @@ void drawLoRaFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, display->setTextAlignment(TEXT_ALIGN_LEFT); // === First Row: Region / BLE Name === - graphics::UIRenderer::drawNodes(display, x, compactFirstLine + 3, nodeStatus, 0, true); + graphics::UIRenderer::drawNodes(display, x, compactFirstLine + 3, nodeStatus, 0, true, ""); uint8_t dmac[6]; char shortnameble[35]; diff --git a/src/graphics/draw/NodeListRenderer.cpp b/src/graphics/draw/NodeListRenderer.cpp index 4385e0f1b..bb7556f02 100644 --- a/src/graphics/draw/NodeListRenderer.cpp +++ b/src/graphics/draw/NodeListRenderer.cpp @@ -49,9 +49,9 @@ static int scrollIndex = 0; // Utility Functions // ============================= -String getSafeNodeName(meshtastic_NodeInfoLite *node) +const char *getSafeNodeName(meshtastic_NodeInfoLite *node) { - String nodeName = "?"; + static char nodeName[16] = "?"; if (node->has_user && strlen(node->user.short_name) > 0) { bool valid = true; const char *name = node->user.short_name; @@ -63,12 +63,13 @@ String getSafeNodeName(meshtastic_NodeInfoLite *node) } } if (valid) { - nodeName = name; + strncpy(nodeName, name, sizeof(nodeName) - 1); + nodeName[sizeof(nodeName) - 1] = '\0'; } else { - char idStr[6]; - snprintf(idStr, sizeof(idStr), "%04X", (uint16_t)(node->num & 0xFFFF)); - nodeName = String(idStr); + snprintf(nodeName, sizeof(nodeName), "%04X", (uint16_t)(node->num & 0xFFFF)); } + } else { + strcpy(nodeName, "?"); } return nodeName; } @@ -181,7 +182,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int bool isLeftCol = (x < SCREEN_WIDTH / 2); int timeOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7); - String nodeName = getSafeNodeName(node); + const char *nodeName = getSafeNodeName(node); char timeStr[10]; uint32_t seconds = sinceLastSeen(node); @@ -224,7 +225,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int int barsXOffset = columnWidth - barsOffset; - String nodeName = getSafeNodeName(node); + const char *nodeName = getSafeNodeName(node); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); @@ -268,7 +269,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 bool isLeftCol = (x < SCREEN_WIDTH / 2); int nameMaxWidth = columnWidth - (SCREEN_WIDTH > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); - String nodeName = getSafeNodeName(node); + const char *nodeName = getSafeNodeName(node); char distStr[10] = ""; meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); @@ -363,7 +364,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16 // Adjust max text width depending on column and screen width int nameMaxWidth = columnWidth - (SCREEN_WIDTH > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); - String nodeName = getSafeNodeName(node); + const char *nodeName = getSafeNodeName(node); display->setTextAlignment(TEXT_ALIGN_LEFT); display->setFont(FONT_SMALL); diff --git a/src/graphics/draw/NodeListRenderer.h b/src/graphics/draw/NodeListRenderer.h index d35350cb8..aa92e34ea 100644 --- a/src/graphics/draw/NodeListRenderer.h +++ b/src/graphics/draw/NodeListRenderer.h @@ -59,7 +59,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in // Utility functions const char *getCurrentModeTitle(int screenWidth); void retrieveAndSortNodes(std::vector &nodeList); -String getSafeNodeName(meshtastic_NodeInfoLite *node); +const char *getSafeNodeName(meshtastic_NodeInfoLite *node); uint32_t sinceLastSeen(meshtastic_NodeInfoLite *node); void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); diff --git a/src/graphics/draw/NotificationRenderer.cpp b/src/graphics/draw/NotificationRenderer.cpp index 6d8423adc..18c1c0d9f 100644 --- a/src/graphics/draw/NotificationRenderer.cpp +++ b/src/graphics/draw/NotificationRenderer.cpp @@ -2,6 +2,7 @@ #include "DisplayFormatters.h" #include "NodeDB.h" #include "configuration.h" +#include "graphics/Screen.h" #include "graphics/ScreenFonts.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" @@ -17,8 +18,6 @@ using namespace meshtastic; // External references to global variables from Screen.cpp -extern String alertBannerMessage; -extern uint32_t alertBannerUntil; extern std::vector functionSymbol; extern std::string functionSymbolString; extern bool hasUnreadMessage; @@ -77,7 +76,7 @@ void NotificationRenderer::drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUi void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) { // Exit if no message is active or duration has passed - if (alertBannerMessage.length() == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil)) + if (strlen(alertBannerMessage) == 0 || (alertBannerUntil != 0 && millis() > alertBannerUntil)) return; // === Layout Configuration === @@ -85,28 +84,37 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp constexpr uint8_t lineSpacing = 1; // Extra space between lines // Search the message to determine if we need the bell added - bool needs_bell = (alertBannerMessage.indexOf("Alert Received") != -1); + bool needs_bell = (strstr(alertBannerMessage, "Alert Received") != nullptr); // Setup font and alignment display->setFont(FONT_SMALL); display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line // === Split the message into lines (supports multi-line banners) === - std::vector lines; - int start = 0, newlineIdx; - while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) { - lines.push_back(alertBannerMessage.substring(start, newlineIdx)); - start = newlineIdx + 1; + const int MAX_LINES = 10; + char lines[MAX_LINES][256]; + int lineCount = 0; + + // Create a working copy of the message to tokenize + char messageCopy[256]; + strncpy(messageCopy, alertBannerMessage, sizeof(messageCopy) - 1); + messageCopy[sizeof(messageCopy) - 1] = '\0'; + + char *line = strtok(messageCopy, "\n"); + while (line != nullptr && lineCount < MAX_LINES) { + strncpy(lines[lineCount], line, sizeof(lines[lineCount]) - 1); + lines[lineCount][sizeof(lines[lineCount]) - 1] = '\0'; + lineCount++; + line = strtok(nullptr, "\n"); } - lines.push_back(alertBannerMessage.substring(start)); // === Measure text dimensions === uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78; uint16_t maxWidth = 0; - std::vector lineWidths; - for (const auto &line : lines) { - uint16_t w = display->getStringWidth(line.c_str(), line.length(), true); - lineWidths.push_back(w); + uint16_t lineWidths[MAX_LINES]; + for (int i = 0; i < lineCount; i++) { + uint16_t w = display->getStringWidth(lines[i], strlen(lines[i]), true); + lineWidths[i] = w; if (w > maxWidth) maxWidth = w; } @@ -115,7 +123,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp if (needs_bell && boxWidth < minWidth) boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20; - uint16_t boxHeight = padding * 2 + lines.size() * FONT_HEIGHT_SMALL + (lines.size() - 1) * lineSpacing; + uint16_t boxHeight = padding * 2 + lineCount * FONT_HEIGHT_SMALL + (lineCount - 1) * lineSpacing; int16_t boxLeft = (display->width() / 2) - (boxWidth / 2); int16_t boxTop = (display->height() / 2) - (boxHeight / 2); @@ -128,9 +136,9 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp // === Draw each line centered in the box === int16_t lineY = boxTop + padding; - for (size_t i = 0; i < lines.size(); ++i) { + for (int i = 0; i < lineCount; i++) { int16_t textX = boxLeft + (boxWidth - lineWidths[i]) / 2; - uint16_t line_width = display->getStringWidth(lines[i].c_str(), lines[i].length(), true); + uint16_t line_width = display->getStringWidth(lines[i], strlen(lines[i]), true); if (needs_bell && i == 0) { int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2; diff --git a/src/graphics/draw/UIRenderer.cpp b/src/graphics/draw/UIRenderer.cpp index f8524c1d1..becc5347d 100644 --- a/src/graphics/draw/UIRenderer.cpp +++ b/src/graphics/draw/UIRenderer.cpp @@ -15,6 +15,7 @@ #include "target_specific.h" #include #include +#include #if !MESHTASTIC_EXCLUDE_GPS @@ -104,7 +105,7 @@ void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSSt // Draw status when GPS is disabled or not present void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps) { - String displayLine; + const char *displayLine; int pos; if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off"; @@ -119,7 +120,7 @@ void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshta void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps) { - String displayLine = ""; + char displayLine[32]; if (!gps->getIsConnected() && !config.position.fixed_position) { // displayLine = "No GPS Module"; // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); @@ -128,9 +129,10 @@ void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtasti // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); } else { geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); - displayLine = "Altitude: " + String(geoCoord.getAltitude()) + "m"; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - displayLine = "Altitude: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; + snprintf(displayLine, sizeof(displayLine), "Altitude: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET); + else + snprintf(displayLine, sizeof(displayLine), "Altitude: %.0fm", geoCoord.getAltitude()); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); } } @@ -139,13 +141,13 @@ void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtasti void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps) { auto gpsFormat = config.display.gps_format; - String displayLine = ""; + char displayLine[32]; if (!gps->getIsConnected() && !config.position.fixed_position) { - displayLine = "No GPS present"; + strcpy(displayLine, "No GPS present"); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); } else if (!gps->getHasLock() && !config.position.fixed_position) { - displayLine = "No GPS Lock"; + strcpy(displayLine, "No GPS Lock"); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine); } else { @@ -260,10 +262,10 @@ void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::Nod #endif display->drawString(x + 10, y - 2, usersString); int string_offset = (SCREEN_WIDTH > 128) ? 2 : 1; - if (additional_words != "") { - display->drawString(x + 10 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words); + if (additional_words.length() > 0) { + display->drawString(x + 10 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words.c_str()); if (config.display.heading_bold) - display->drawString(x + 11 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words); + display->drawString(x + 11 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words.c_str()); } } @@ -616,7 +618,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t #if HAS_GPS if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { - String displayLine = ""; + const char *displayLine; if (config.position.fixed_position) { displayLine = "Fixed GPS"; } else { @@ -644,8 +646,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t } else { display->drawString( x + SCREEN_WIDTH - display->getStringWidth("USB"), - ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), - String("USB")); + ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), "USB"); } config.display.heading_bold = origBold; @@ -975,9 +976,9 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat bool origBold = config.display.heading_bold; config.display.heading_bold = false; - String Satelite_String = "Sat:"; + const char *Satelite_String = "Sat:"; display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), Satelite_String); - String displayLine = ""; + const char *displayLine = ""; // Initialize to empty string by default if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.fixed_position) { displayLine = "Fixed GPS"; @@ -987,6 +988,7 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat display->drawString(display->getStringWidth(Satelite_String) + 3, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine); } else { + displayLine = "GPS enabled"; // Set a value when GPS is enabled UIRenderer::drawGps(display, display->getStringWidth(Satelite_String) + 3, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine) + 3, gpsStatus); } @@ -1010,13 +1012,15 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat } // If GPS is off, no need to display these parts - if (displayLine != "GPS off" && displayLine != "No GPS") { + if (strcmp(displayLine, "GPS off") != 0 && strcmp(displayLine, "No GPS") != 0) { // === Second Row: Altitude === - String displayLine; - displayLine = " Alt: " + String(geoCoord.getAltitude()) + "m"; - if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) - displayLine = " Alt: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; + char displayLine[32]; + if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) { + snprintf(displayLine, sizeof(displayLine), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET); + } else { + snprintf(displayLine, sizeof(displayLine), " Alt: %.0fm", geoCoord.getAltitude()); + } display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine), displayLine); // === Third Row: Latitude === diff --git a/src/modules/CannedMessageModule.cpp b/src/modules/CannedMessageModule.cpp index 99813cba6..254e417f9 100644 --- a/src/modules/CannedMessageModule.cpp +++ b/src/modules/CannedMessageModule.cpp @@ -11,6 +11,7 @@ #include "PowerFSM.h" // needed for button bypass #include "SPILock.h" #include "detect/ScanI2C.h" +#include "graphics/Screen.h" #include "graphics/SharedUIDisplay.h" #include "graphics/images.h" #include "input/ScanAndSelect.h" @@ -720,9 +721,7 @@ bool CannedMessageModule::handleSystemCommandInput(const InputEvent *event) return false; // Block ALL input if an alert banner is active - extern String alertBannerMessage; - extern uint32_t alertBannerUntil; - if (alertBannerMessage.length() > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil)) { + if (strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil)) { return true; } @@ -1381,28 +1380,32 @@ void CannedMessageModule::drawDestinationSelectionScreen(OLEDDisplay *display, O if (itemIndex >= totalEntries) break; - int xOffset = 0; - int yOffset = row * (FONT_HEIGHT_SMALL - 4) + rowYOffset; - String entryText; + int xOffset = 0; + int yOffset = row * (FONT_HEIGHT_SMALL - 4) + rowYOffset; + char entryText[64]; - // Draw Channels First - if (itemIndex < numActiveChannels) { - uint8_t channelIndex = this->activeChannelIndices[itemIndex]; - entryText = String("@") + String(channels.getName(channelIndex)); - } - // Then Draw Nodes - else { - int nodeIndex = itemIndex - numActiveChannels; - if (nodeIndex >= 0 && nodeIndex < static_cast(this->filteredNodes.size())) { - meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node; - if (node) { - entryText = node->is_favorite ? "* " + String(node->user.long_name) : String(node->user.long_name); + // Draw Channels First + if (itemIndex < numActiveChannels) { + uint8_t channelIndex = this->activeChannelIndices[itemIndex]; + snprintf(entryText, sizeof(entryText), "@%s", channels.getName(channelIndex)); + } + // Then Draw Nodes + else { + int nodeIndex = itemIndex - numActiveChannels; + if (nodeIndex >= 0 && nodeIndex < static_cast(this->filteredNodes.size())) { + meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node; + if (node) { + if (node->is_favorite) { + snprintf(entryText, sizeof(entryText), "* %s", node->user.long_name); + } else { + snprintf(entryText, sizeof(entryText), "%s", node->user.long_name); + } + } } } - } - if (entryText.length() == 0 || entryText == "Unknown") - entryText = "?"; + if (strlen(entryText) == 0 || strcmp(entryText, "Unknown") == 0) + strcpy(entryText, "?"); // === Highlight background (if selected) === if (itemIndex == destIndex) { diff --git a/src/modules/SerialModule.cpp b/src/modules/SerialModule.cpp index 8d280581c..f3921ef19 100644 --- a/src/modules/SerialModule.cpp +++ b/src/modules/SerialModule.cpp @@ -341,7 +341,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp serialPrint->write(p.payload.bytes, p.payload.size); } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) { meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); - String sender = (node && node->has_user) ? node->user.short_name : "???"; + const char *sender = (node && node->has_user) ? node->user.short_name : "???"; serialPrint->println(); serialPrint->printf("%s: %s", sender, p.payload.bytes); serialPrint->println(); @@ -410,8 +410,8 @@ uint32_t SerialModule::getBaudRate() // Add this structure to help with parsing WindGust = 24.4 serial lines. struct ParsedLine { - String name; - String value; + char name[64]; + char value[128]; }; /** @@ -438,16 +438,30 @@ ParsedLine parseLine(const char *line) strncpy(nameBuf, line, nameLen); nameBuf[nameLen] = '\0'; - // Create trimmed name string - String name = String(nameBuf); - name.trim(); + // Trim whitespace from name + char *nameStart = nameBuf; + while (*nameStart && isspace(*nameStart)) + nameStart++; + char *nameEnd = nameStart + strlen(nameStart) - 1; + while (nameEnd > nameStart && isspace(*nameEnd)) + *nameEnd-- = '\0'; - // Extract value after equals sign - String value = String(equals + 1); - value.trim(); + // Copy trimmed name + strncpy(result.name, nameStart, sizeof(result.name) - 1); + result.name[sizeof(result.name) - 1] = '\0'; + + // Extract value part (after equals) + const char *valueStart = equals + 1; + while (*valueStart && isspace(*valueStart)) + valueStart++; + strncpy(result.value, valueStart, sizeof(result.value) - 1); + result.value[sizeof(result.value) - 1] = '\0'; + + // Trim trailing whitespace from value + char *valueEnd = result.value + strlen(result.value) - 1; + while (valueEnd > result.value && isspace(*valueEnd)) + *valueEnd-- = '\0'; - result.name = name; - result.value = value; return result; } @@ -517,16 +531,16 @@ void SerialModule::processWXSerial() memcpy(line, &serialBytes[lineStart], lineEnd - lineStart); ParsedLine parsed = parseLine(line); - if (parsed.name.length() > 0) { - if (parsed.name == "WindDir") { - strlcpy(windDir, parsed.value.c_str(), sizeof(windDir)); + if (strlen(parsed.name) > 0) { + if (strcmp(parsed.name, "WindDir") == 0) { + strlcpy(windDir, parsed.value, sizeof(windDir)); double radians = GeoCoord::toRadians(strtof(windDir, nullptr)); dir_sum_sin += sin(radians); dir_sum_cos += cos(radians); dirCount++; gotwind = true; - } else if (parsed.name == "WindSpeed") { - strlcpy(windVel, parsed.value.c_str(), sizeof(windVel)); + } else if (strcmp(parsed.name, "WindSpeed") == 0) { + strlcpy(windVel, parsed.value, sizeof(windVel)); float newv = strtof(windVel, nullptr); velSum += newv; velCount++; @@ -534,28 +548,28 @@ void SerialModule::processWXSerial() lull = newv; } gotwind = true; - } else if (parsed.name == "WindGust") { - strlcpy(windGust, parsed.value.c_str(), sizeof(windGust)); + } else if (strcmp(parsed.name, "WindGust") == 0) { + strlcpy(windGust, parsed.value, sizeof(windGust)); float newg = strtof(windGust, nullptr); if (newg > gust) { gust = newg; } gotwind = true; - } else if (parsed.name == "BatVoltage") { - strlcpy(batVoltage, parsed.value.c_str(), sizeof(batVoltage)); + } else if (strcmp(parsed.name, "BatVoltage") == 0) { + strlcpy(batVoltage, parsed.value, sizeof(batVoltage)); batVoltageF = strtof(batVoltage, nullptr); break; // last possible data we want so break - } else if (parsed.name == "CapVoltage") { - strlcpy(capVoltage, parsed.value.c_str(), sizeof(capVoltage)); + } else if (strcmp(parsed.name, "CapVoltage") == 0) { + strlcpy(capVoltage, parsed.value, sizeof(capVoltage)); capVoltageF = strtof(capVoltage, nullptr); - } else if (parsed.name == "GXTS04Temp" || parsed.name == "Temperature") { - strlcpy(temperature, parsed.value.c_str(), sizeof(temperature)); + } else if (strcmp(parsed.name, "GXTS04Temp") == 0 || strcmp(parsed.name, "Temperature") == 0) { + strlcpy(temperature, parsed.value, sizeof(temperature)); temperatureF = strtof(temperature, nullptr); - } else if (parsed.name == "RainIntSum") { - strlcpy(rainStr, parsed.value.c_str(), sizeof(rainStr)); + } else if (strcmp(parsed.name, "RainIntSum") == 0) { + strlcpy(rainStr, parsed.value, sizeof(rainStr)); rainSum = int(strtof(rainStr, nullptr)); - } else if (parsed.name == "Rain") { - strlcpy(rainStr, parsed.value.c_str(), sizeof(rainStr)); + } else if (strcmp(parsed.name, "Rain") == 0) { + strlcpy(rainStr, parsed.value, sizeof(rainStr)); rain = strtof(rainStr, nullptr); } } diff --git a/src/modules/Telemetry/HealthTelemetry.cpp b/src/modules/Telemetry/HealthTelemetry.cpp index a2a18ba03..3a735b1fa 100644 --- a/src/modules/Telemetry/HealthTelemetry.cpp +++ b/src/modules/Telemetry/HealthTelemetry.cpp @@ -118,22 +118,31 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState * } // Display "Health From: ..." on its own - display->drawString(x, y, "Health From: " + String(lastSender) + "(" + String(agoSecs) + "s)"); + char headerStr[64]; + snprintf(headerStr, sizeof(headerStr), "Health From: %s(%ds)", lastSender, (int)agoSecs); + display->drawString(x, y, headerStr); - String last_temp = String(lastMeasurement.variant.health_metrics.temperature, 0) + "°C"; + char last_temp[16]; if (moduleConfig.telemetry.environment_display_fahrenheit) { - last_temp = String(UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.health_metrics.temperature), 0) + "°F"; + snprintf(last_temp, sizeof(last_temp), "%.0f°F", + UnitConversions::CelsiusToFahrenheit(lastMeasurement.variant.health_metrics.temperature)); + } else { + snprintf(last_temp, sizeof(last_temp), "%.0f°C", lastMeasurement.variant.health_metrics.temperature); } // Continue with the remaining details - display->drawString(x, y += _fontHeight(FONT_SMALL), "Temp: " + last_temp); + char tempStr[32]; + snprintf(tempStr, sizeof(tempStr), "Temp: %s", last_temp); + display->drawString(x, y += _fontHeight(FONT_SMALL), tempStr); if (lastMeasurement.variant.health_metrics.has_heart_bpm) { - display->drawString(x, y += _fontHeight(FONT_SMALL), - "Heart Rate: " + String(lastMeasurement.variant.health_metrics.heart_bpm, 0) + " bpm"); + char heartStr[32]; + snprintf(heartStr, sizeof(heartStr), "Heart Rate: %.0f bpm", lastMeasurement.variant.health_metrics.heart_bpm); + display->drawString(x, y += _fontHeight(FONT_SMALL), heartStr); } if (lastMeasurement.variant.health_metrics.has_spO2) { - display->drawString(x, y += _fontHeight(FONT_SMALL), - "spO2: " + String(lastMeasurement.variant.health_metrics.spO2, 0) + " %"); + char spo2Str[32]; + snprintf(spo2Str, sizeof(spo2Str), "spO2: %.0f %%", lastMeasurement.variant.health_metrics.spO2); + display->drawString(x, y += _fontHeight(FONT_SMALL), spo2Str); } } diff --git a/src/modules/Telemetry/PowerTelemetry.cpp b/src/modules/Telemetry/PowerTelemetry.cpp index e42d718a5..e635b022d 100644 --- a/src/modules/Telemetry/PowerTelemetry.cpp +++ b/src/modules/Telemetry/PowerTelemetry.cpp @@ -152,14 +152,18 @@ void PowerTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *s } // Display "Pow. From: ..." - display->drawString(x, compactFirstLine, "Pow. From: " + String(lastSender) + " (" + String(agoSecs) + "s)"); + char fromStr[64]; + snprintf(fromStr, sizeof(fromStr), "Pow. From: %s (%ds)", lastSender, agoSecs); + display->drawString(x, compactFirstLine, fromStr); // Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags const auto &m = lastMeasurement.variant.power_metrics; int lineY = compactSecondLine; auto drawLine = [&](const char *label, float voltage, float current) { - display->drawString(x, lineY, String(label) + ": " + String(voltage, 2) + "V " + String(current, 0) + "mA"); + char lineStr[64]; + snprintf(lineStr, sizeof(lineStr), "%s: %.2fV %.0fmA", label, voltage, current); + display->drawString(x, lineY, lineStr); lineY += _fontHeight(FONT_SMALL); }; diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.cpp b/src/modules/Telemetry/Sensor/BME680Sensor.cpp index 0e0212bc5..fce029deb 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.cpp +++ b/src/modules/Telemetry/Sensor/BME680Sensor.cpp @@ -137,17 +137,17 @@ void BME680Sensor::updateState() #endif } -void BME680Sensor::checkStatus(String functionName) +void BME680Sensor::checkStatus(const char *functionName) { if (bme680.status < BSEC_OK) - LOG_ERROR("%s BSEC2 code: %s", functionName.c_str(), String(bme680.status).c_str()); + LOG_ERROR("%s BSEC2 code: %d", functionName, bme680.status); else if (bme680.status > BSEC_OK) - LOG_WARN("%s BSEC2 code: %s", functionName.c_str(), String(bme680.status).c_str()); + LOG_WARN("%s BSEC2 code: %d", functionName, bme680.status); if (bme680.sensor.status < BME68X_OK) - LOG_ERROR("%s BME68X code: %s", functionName.c_str(), String(bme680.sensor.status).c_str()); + LOG_ERROR("%s BME68X code: %d", functionName, bme680.sensor.status); else if (bme680.sensor.status > BME68X_OK) - LOG_WARN("%s BME68X code: %s", functionName.c_str(), String(bme680.sensor.status).c_str()); + LOG_WARN("%s BME68X code: %d", functionName, bme680.sensor.status); } #endif diff --git a/src/modules/Telemetry/Sensor/BME680Sensor.h b/src/modules/Telemetry/Sensor/BME680Sensor.h index 249c4b3e7..ce1fa4f3b 100644 --- a/src/modules/Telemetry/Sensor/BME680Sensor.h +++ b/src/modules/Telemetry/Sensor/BME680Sensor.h @@ -34,7 +34,7 @@ class BME680Sensor : public TelemetrySensor BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY}; void loadState(); void updateState(); - void checkStatus(String functionName); + void checkStatus(const char *functionName); public: BME680Sensor(); diff --git a/src/nimble/NimbleBluetooth.cpp b/src/nimble/NimbleBluetooth.cpp index 80787092d..ef834db37 100644 --- a/src/nimble/NimbleBluetooth.cpp +++ b/src/nimble/NimbleBluetooth.cpp @@ -109,14 +109,14 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks display->drawString(x_offset + x, y_offset + y, "Enter this code"); display->setFont(FONT_LARGE); - String displayPin(btPIN); - String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); + char pin[8]; + snprintf(pin, sizeof(pin), "%.3s %.3s", btPIN, btPIN + 3); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_SMALL - 5 : y_offset + FONT_HEIGHT_SMALL + 5; display->drawString(x_offset + x, y_offset + y, pin); display->setFont(FONT_SMALL); - String deviceName = "Name: "; - deviceName.concat(getDeviceName()); + char deviceName[64]; + snprintf(deviceName, sizeof(deviceName), "Name: %s", getDeviceName()); y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; display->drawString(x_offset + x, y_offset + y, deviceName); }); diff --git a/src/platform/esp32/WiFiOTA.cpp b/src/platform/esp32/WiFiOTA.cpp index eac124dda..4cf157b4c 100644 --- a/src/platform/esp32/WiFiOTA.cpp +++ b/src/platform/esp32/WiFiOTA.cpp @@ -80,13 +80,13 @@ bool trySwitchToOTA() return true; } -String getVersion() +const char *getVersion() { const esp_partition_t *part = getAppPartition(); - esp_app_desc_t app_desc; + static esp_app_desc_t app_desc; if (!getAppDesc(part, &app_desc)) - return String(); - return String(app_desc.version); + return ""; + return app_desc.version; } } // namespace WiFiOTA diff --git a/src/platform/esp32/WiFiOTA.h b/src/platform/esp32/WiFiOTA.h index 61860ed5e..5a7ee348a 100644 --- a/src/platform/esp32/WiFiOTA.h +++ b/src/platform/esp32/WiFiOTA.h @@ -12,7 +12,7 @@ bool isUpdated(); void recoverConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network); bool trySwitchToOTA(); -String getVersion(); +const char *getVersion(); } // namespace WiFiOTA #endif // WIFIOTA_H