Get rid of Arduino Strings

This commit is contained in:
Ben Meadors 2025-06-01 15:54:54 -05:00 committed by Jonathan Bennett
parent 6344be0a1e
commit 8cdb34a299
16 changed files with 237 additions and 176 deletions

View File

@ -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<const char *>(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);

View File

@ -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<std::string> functionSymbol;

View File

@ -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];

View File

@ -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);

View File

@ -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<NodeEntry> &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);

View File

@ -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<std::string> 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<String> 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<uint16_t> 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;

View File

@ -15,6 +15,7 @@
#include "target_specific.h"
#include <OLEDDisplay.h>
#include <RTC.h>
#include <cstring>
#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 ===

View File

@ -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<int>(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<int>(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) {

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
};

View File

@ -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

View File

@ -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();

View File

@ -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);
});

View File

@ -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

View File

@ -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