Get rid of Arduino Strings

This commit is contained in:
Ben Meadors 2025-06-01 15:54:54 -05:00
parent 21404de7fc
commit 6749d9ffc5
16 changed files with 224 additions and 163 deletions

View File

@ -80,9 +80,6 @@ using graphics::numEmotes;
using namespace meshtastic; /** @todo remove */ using namespace meshtastic; /** @todo remove */
String alertBannerMessage = "";
uint32_t alertBannerUntil = 0;
namespace graphics namespace graphics
{ {
@ -97,8 +94,12 @@ namespace graphics
// A text message frame + debug frame + all the node infos // A text message frame + debug frame + all the node infos
FrameCallback *normalFrames; FrameCallback *normalFrames;
static uint32_t targetFramerate = IDLE_FRAMERATE; static uint32_t targetFramerate = IDLE_FRAMERATE;
static String alertBannerMessage; // Global variables for alert banner - explicitly define with extern "C" linkage to prevent optimization
static uint32_t alertBannerUntil = 0; extern "C" {
static char alertBannerBuffer[256] = "";
char *alertBannerMessage = alertBannerBuffer;
uint32_t alertBannerUntil = 0;
}
uint32_t logo_timeout = 5000; // 4 seconds for EACH logo 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 // 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 // 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 // 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; alertBannerUntil = (durationMs == 0) ? 0 : millis() + durationMs;
} }
@ -225,7 +227,8 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
drawBattery(display, x, y + 7, imgBattery, powerStatus); drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) { if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; char batteryPercent[8];
snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent());
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@ -255,16 +258,13 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
hour = 12; hour = 12;
} }
// hours string // Format time string
String hourString = String(hour); char timeString[16];
snprintf(timeString, sizeof(timeString), "%d:%02d", hour, minute);
// minutes string // Format seconds string
String minuteString = minute < 10 ? "0" + String(minute) : String(minute); char secondString[8];
snprintf(secondString, sizeof(secondString), "%02d", second);
String timeString = hourString + ":" + minuteString;
// seconds string
String secondString = second < 10 ? "0" + String(second) : String(second);
float scale = 1.5; float scale = 1.5;
@ -272,12 +272,12 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
uint16_t segmentHeight = SEGMENT_HEIGHT * scale; uint16_t segmentHeight = SEGMENT_HEIGHT * scale;
// calculate hours:minutes string width // 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++) { for (uint8_t i = 0; i < strlen(timeString); i++) {
String character = String(timeString[i]); char character = timeString[i];
if (character == ":") { if (character == ':') {
timeStringWidth += segmentHeight; timeStringWidth += segmentHeight;
} else { } else {
timeStringWidth += segmentWidth + (segmentHeight * 2) + 4; timeStringWidth += segmentWidth + (segmentHeight * 2) + 4;
@ -285,7 +285,7 @@ void Screen::drawDigitalClockFrame(OLEDDisplay *display, OLEDDisplayUiState *sta
} }
// calculate seconds string width // 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 // sum these to get total string width
uint16_t totalWidth = timeStringWidth + secondStringWidth; 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); uint16_t hourMinuteTextY = (display->getHeight() / 2) - (((segmentWidth * 2) + (segmentHeight * 3) + 8) / 2);
// iterate over characters in hours:minutes string and draw segmented characters // iterate over characters in hours:minutes string and draw segmented characters
for (uint8_t i = 0; i < timeString.length(); i++) { for (uint8_t i = 0; i < strlen(timeString); i++) {
String character = String(timeString[i]); char character = timeString[i];
if (character == ":") { if (character == ':') {
drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale); drawSegmentedDisplayColon(display, hourMinuteTextX, hourMinuteTextY, scale);
hourMinuteTextX += segmentHeight + 6; hourMinuteTextX += segmentHeight + 6;
} else { } else {
drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character.toInt(), scale); drawSegmentedDisplayCharacter(display, hourMinuteTextX, hourMinuteTextY, character - '0', scale);
hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4; hourMinuteTextX += segmentWidth + (segmentHeight * 2) + 4;
} }
@ -457,7 +457,8 @@ void Screen::drawAnalogClockFrame(OLEDDisplay *display, OLEDDisplayUiState *stat
drawBattery(display, x, y + 7, imgBattery, powerStatus); drawBattery(display, x, y + 7, imgBattery, powerStatus);
if (powerStatus->getHasBattery()) { if (powerStatus->getHasBattery()) {
String batteryPercent = String(powerStatus->getBatteryChargePercent()) + "%"; char batteryPercent[8];
snprintf(batteryPercent, sizeof(batteryPercent), "%d%%", powerStatus->getBatteryChargePercent());
display->setFont(FONT_SMALL); 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 *longName = (node && node->has_user) ? node->user.long_name : nullptr;
const char *msgRaw = reinterpret_cast<const char *>(packet->decoded.payload.bytes); 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 // Check for bell character in message to determine alert type
if (msg.indexOf("\x07") != -1) { bool isAlert = false;
banner = "Alert Received"; for (size_t i = 0; i < packet->decoded.payload.size && i < 100; i++) {
} else { if (msgRaw[i] == '\x07') {
banner = "New Message"; isAlert = true;
break;
}
} }
if (longName && longName[0]) { if (isAlert) {
banner += "\nfrom "; if (longName && longName[0]) {
banner += longName; 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); screen->showOverlayBanner(banner, 3000);

View File

@ -269,7 +269,7 @@ class Screen : public concurrency::OSThread
enqueueCmd(cmd); enqueueCmd(cmd);
} }
void showOverlayBanner(const String &message, uint32_t durationMs = 3000); void showOverlayBanner(const char *message, uint32_t durationMs = 3000);
void startFirmwareUpdateScreen() void startFirmwareUpdateScreen()
{ {
@ -688,8 +688,10 @@ class Screen : public concurrency::OSThread
} // namespace graphics } // namespace graphics
extern String alertBannerMessage; extern "C" {
extern char *alertBannerMessage;
extern uint32_t alertBannerUntil; extern uint32_t alertBannerUntil;
}
// Extern declarations for function symbols used in UIRenderer // Extern declarations for function symbols used in UIRenderer
extern std::vector<std::string> functionSymbol; 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) { 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) if (config.display.heading_bold)
display->drawString(x + 1, y, String("WiFi: Not Connected")); display->drawString(x + 1, y, "WiFi: Not Connected");
} else { } else {
display->drawString(x, y, String("WiFi: Connected")); display->drawString(x, y, "WiFi: Connected");
if (config.display.heading_bold) 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, char rssiStr[32];
"RSSI " + String(WiFi.RSSI())); snprintf(rssiStr, sizeof(rssiStr), "RSSI %d", WiFi.RSSI());
display->drawString(x + SCREEN_WIDTH - display->getStringWidth(rssiStr), y, rssiStr);
if (config.display.heading_bold) { if (config.display.heading_bold) {
display->drawString(x + SCREEN_WIDTH - display->getStringWidth("RSSI " + String(WiFi.RSSI())) - 1, y, display->drawString(x + SCREEN_WIDTH - display->getStringWidth(rssiStr) - 1, y, rssiStr);
"RSSI " + String(WiFi.RSSI()));
} }
} }
@ -212,7 +212,9 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
*/ */
if (WiFi.status() == WL_CONNECTED) { 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) { } else if (WiFi.status() == WL_NO_SSID_AVAIL) {
display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found"); display->drawString(x, y + FONT_HEIGHT_SMALL * 1, "SSID Not Found");
} else if (WiFi.status() == WL_CONNECTION_LOST) { } else if (WiFi.status() == WL_CONNECTION_LOST) {
@ -231,11 +233,15 @@ void drawFrameWiFi(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, i
} }
#else #else
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 #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"); 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); display->drawString(x + 1, y, batStr);
} else { } else {
// Line 1 // Line 1
display->drawString(x, y, String("USB")); display->drawString(x, y, "USB");
if (config.display.heading_bold) 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); // 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); display->setTextAlignment(TEXT_ALIGN_LEFT);
// === First Row: Region / BLE Name === // === 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]; uint8_t dmac[6];
char shortnameble[35]; char shortnameble[35];

View File

@ -47,9 +47,9 @@ static int scrollIndex = 0;
// Utility Functions // 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) { if (node->has_user && strlen(node->user.short_name) > 0) {
bool valid = true; bool valid = true;
const char *name = node->user.short_name; const char *name = node->user.short_name;
@ -61,12 +61,13 @@ String getSafeNodeName(meshtastic_NodeInfoLite *node)
} }
} }
if (valid) { if (valid) {
nodeName = name; strncpy(nodeName, name, sizeof(nodeName) - 1);
nodeName[sizeof(nodeName) - 1] = '\0';
} else { } else {
char idStr[6]; snprintf(nodeName, sizeof(nodeName), "%04X", (uint16_t)(node->num & 0xFFFF));
snprintf(idStr, sizeof(idStr), "%04X", (uint16_t)(node->num & 0xFFFF));
nodeName = String(idStr);
} }
} else {
strcpy(nodeName, "?");
} }
return nodeName; return nodeName;
} }
@ -179,7 +180,7 @@ void drawEntryLastHeard(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
bool isLeftCol = (x < SCREEN_WIDTH / 2); bool isLeftCol = (x < SCREEN_WIDTH / 2);
int timeOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7); int timeOffset = (SCREEN_WIDTH > 128) ? (isLeftCol ? 7 : 10) : (isLeftCol ? 3 : 7);
String nodeName = getSafeNodeName(node); const char *nodeName = getSafeNodeName(node);
char timeStr[10]; char timeStr[10];
uint32_t seconds = sinceLastSeen(node); uint32_t seconds = sinceLastSeen(node);
@ -222,7 +223,7 @@ void drawEntryHopSignal(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int
int barsXOffset = columnWidth - barsOffset; int barsXOffset = columnWidth - barsOffset;
String nodeName = getSafeNodeName(node); const char *nodeName = getSafeNodeName(node);
display->setTextAlignment(TEXT_ALIGN_LEFT); display->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
@ -266,7 +267,7 @@ void drawNodeDistance(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
bool isLeftCol = (x < SCREEN_WIDTH / 2); bool isLeftCol = (x < SCREEN_WIDTH / 2);
int nameMaxWidth = columnWidth - (SCREEN_WIDTH > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); int nameMaxWidth = columnWidth - (SCREEN_WIDTH > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22));
String nodeName = getSafeNodeName(node); const char *nodeName = getSafeNodeName(node);
char distStr[10] = ""; char distStr[10] = "";
meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum()); meshtastic_NodeInfoLite *ourNode = nodeDB->getMeshNode(nodeDB->getNodeNum());
@ -361,7 +362,7 @@ void drawEntryCompass(OLEDDisplay *display, meshtastic_NodeInfoLite *node, int16
// Adjust max text width depending on column and screen width // Adjust max text width depending on column and screen width
int nameMaxWidth = columnWidth - (SCREEN_WIDTH > 128 ? (isLeftCol ? 25 : 28) : (isLeftCol ? 20 : 22)); 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->setTextAlignment(TEXT_ALIGN_LEFT);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);

View File

@ -59,7 +59,7 @@ void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, in
// Utility functions // Utility functions
const char *getCurrentModeTitle(int screenWidth); const char *getCurrentModeTitle(int screenWidth);
void retrieveAndSortNodes(std::vector<NodeEntry> &nodeList); void retrieveAndSortNodes(std::vector<NodeEntry> &nodeList);
String getSafeNodeName(meshtastic_NodeInfoLite *node); const char *getSafeNodeName(meshtastic_NodeInfoLite *node);
uint32_t sinceLastSeen(meshtastic_NodeInfoLite *node); uint32_t sinceLastSeen(meshtastic_NodeInfoLite *node);
void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields); void drawColumns(OLEDDisplay *display, int16_t x, int16_t y, const char **fields);

View File

@ -2,6 +2,7 @@
#include "DisplayFormatters.h" #include "DisplayFormatters.h"
#include "NodeDB.h" #include "NodeDB.h"
#include "configuration.h" #include "configuration.h"
#include "graphics/Screen.h"
#include "graphics/ScreenFonts.h" #include "graphics/ScreenFonts.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/images.h" #include "graphics/images.h"
@ -17,8 +18,6 @@
using namespace meshtastic; using namespace meshtastic;
// External references to global variables from Screen.cpp // External references to global variables from Screen.cpp
extern String alertBannerMessage;
extern uint32_t alertBannerUntil;
extern std::vector<std::string> functionSymbol; extern std::vector<std::string> functionSymbol;
extern std::string functionSymbolString; extern std::string functionSymbolString;
extern bool hasUnreadMessage; extern bool hasUnreadMessage;
@ -77,7 +76,7 @@ void NotificationRenderer::drawWelcomeScreen(OLEDDisplay *display, OLEDDisplayUi
void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state) void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisplayUiState *state)
{ {
// Exit if no message is active or duration has passed // 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; return;
// === Layout Configuration === // === Layout Configuration ===
@ -85,28 +84,37 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
constexpr uint8_t lineSpacing = 1; // Extra space between lines constexpr uint8_t lineSpacing = 1; // Extra space between lines
// Search the message to determine if we need the bell added // 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 // Setup font and alignment
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line display->setTextAlignment(TEXT_ALIGN_LEFT); // We will manually center per line
// === Split the message into lines (supports multi-line banners) === // === Split the message into lines (supports multi-line banners) ===
std::vector<String> lines; const int MAX_LINES = 10;
int start = 0, newlineIdx; char lines[MAX_LINES][256];
while ((newlineIdx = alertBannerMessage.indexOf('\n', start)) != -1) { int lineCount = 0;
lines.push_back(alertBannerMessage.substring(start, newlineIdx));
start = newlineIdx + 1; // 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 === // === Measure text dimensions ===
uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78; uint16_t minWidth = (SCREEN_WIDTH > 128) ? 106 : 78;
uint16_t maxWidth = 0; uint16_t maxWidth = 0;
std::vector<uint16_t> lineWidths; uint16_t lineWidths[MAX_LINES];
for (const auto &line : lines) { for (int i = 0; i < lineCount; i++) {
uint16_t w = display->getStringWidth(line.c_str(), line.length(), true); uint16_t w = display->getStringWidth(lines[i], strlen(lines[i]), true);
lineWidths.push_back(w); lineWidths[i] = w;
if (w > maxWidth) if (w > maxWidth)
maxWidth = w; maxWidth = w;
} }
@ -115,7 +123,7 @@ void NotificationRenderer::drawAlertBannerOverlay(OLEDDisplay *display, OLEDDisp
if (needs_bell && boxWidth < minWidth) if (needs_bell && boxWidth < minWidth)
boxWidth += (SCREEN_WIDTH > 128) ? 26 : 20; 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 boxLeft = (display->width() / 2) - (boxWidth / 2);
int16_t boxTop = (display->height() / 2) - (boxHeight / 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 === // === Draw each line centered in the box ===
int16_t lineY = boxTop + padding; 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; 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) { if (needs_bell && i == 0) {
int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2; int bellY = lineY + (FONT_HEIGHT_SMALL - 8) / 2;

View File

@ -13,6 +13,7 @@
#include "target_specific.h" #include "target_specific.h"
#include <OLEDDisplay.h> #include <OLEDDisplay.h>
#include <RTC.h> #include <RTC.h>
#include <cstring>
#if !MESHTASTIC_EXCLUDE_GPS #if !MESHTASTIC_EXCLUDE_GPS
@ -102,7 +103,7 @@ void drawGps(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSSt
// Draw status when GPS is disabled or not present // Draw status when GPS is disabled or not present
void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps) void drawGpsPowerStatus(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
{ {
String displayLine; const char *displayLine;
int pos; int pos;
if (y < FONT_HEIGHT_SMALL) { // Line 1: use short string 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"; displayLine = config.position.gps_mode == meshtastic_Config_PositionConfig_GpsMode_NOT_PRESENT ? "No GPS" : "GPS off";
@ -117,7 +118,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) 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) { if (!gps->getIsConnected() && !config.position.fixed_position) {
// displayLine = "No GPS Module"; // displayLine = "No GPS Module";
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
@ -126,9 +127,10 @@ void drawGpsAltitude(OLEDDisplay *display, int16_t x, int16_t y, const meshtasti
// display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine); // display->drawString(x + (SCREEN_WIDTH - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else { } else {
geoCoord.updateCoords(int32_t(gps->getLatitude()), int32_t(gps->getLongitude()), int32_t(gps->getAltitude())); 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) 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); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} }
} }
@ -137,13 +139,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) void drawGpsCoordinates(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::GPSStatus *gps)
{ {
auto gpsFormat = config.display.gps_format; auto gpsFormat = config.display.gps_format;
String displayLine = ""; char displayLine[32];
if (!gps->getIsConnected() && !config.position.fixed_position) { 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); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else if (!gps->getHasLock() && !config.position.fixed_position) { } 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); display->drawString(x + (display->getWidth() - (display->getStringWidth(displayLine))) / 2, y, displayLine);
} else { } else {
@ -219,10 +221,10 @@ void drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const meshtastic::Nod
#endif #endif
display->drawString(x + 10, y - 2, usersString); display->drawString(x + 10, y - 2, usersString);
int string_offset = (SCREEN_WIDTH > 128) ? 2 : 1; int string_offset = (SCREEN_WIDTH > 128) ? 2 : 1;
if (additional_words != "") { if (additional_words.length() > 0) {
display->drawString(x + 10 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words); display->drawString(x + 10 + display->getStringWidth(usersString) + string_offset, y - 2, additional_words.c_str());
if (config.display.heading_bold) 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());
} }
} }
@ -575,7 +577,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
#if HAS_GPS #if HAS_GPS
if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) { if (config.position.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
String displayLine = ""; const char *displayLine;
if (config.position.fixed_position) { if (config.position.fixed_position) {
displayLine = "Fixed GPS"; displayLine = "Fixed GPS";
} else { } else {
@ -603,8 +605,7 @@ void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t
} else { } else {
display->drawString( display->drawString(
x + SCREEN_WIDTH - display->getStringWidth("USB"), x + SCREEN_WIDTH - display->getStringWidth("USB"),
((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), ((rows == 4) ? compactSecondLine : ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine)), "USB");
String("USB"));
} }
config.display.heading_bold = origBold; config.display.heading_bold = origBold;
@ -934,9 +935,9 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
bool origBold = config.display.heading_bold; bool origBold = config.display.heading_bold;
config.display.heading_bold = false; config.display.heading_bold = false;
String Satelite_String = "Sat:"; const char *Satelite_String = "Sat:";
display->drawString(0, ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), Satelite_String); 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.gps_mode != meshtastic_Config_PositionConfig_GpsMode_ENABLED) {
if (config.position.fixed_position) { if (config.position.fixed_position) {
displayLine = "Fixed GPS"; displayLine = "Fixed GPS";
@ -946,6 +947,7 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
display->drawString(display->getStringWidth(Satelite_String) + 3, display->drawString(display->getStringWidth(Satelite_String) + 3,
((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine); ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine), displayLine);
} else { } else {
displayLine = "GPS enabled"; // Set a value when GPS is enabled
UIRenderer::drawGps(display, display->getStringWidth(Satelite_String) + 3, UIRenderer::drawGps(display, display->getStringWidth(Satelite_String) + 3,
((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine) + 3, gpsStatus); ((SCREEN_HEIGHT > 64) ? compactFirstLine : moreCompactFirstLine) + 3, gpsStatus);
} }
@ -969,13 +971,15 @@ void drawCompassAndLocationScreen(OLEDDisplay *display, OLEDDisplayUiState *stat
} }
// If GPS is off, no need to display these parts // 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 === // === Second Row: Altitude ===
String displayLine; char displayLine[32];
displayLine = " Alt: " + String(geoCoord.getAltitude()) + "m"; if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) {
if (config.display.units == meshtastic_Config_DisplayConfig_DisplayUnits_IMPERIAL) snprintf(displayLine, sizeof(displayLine), " Alt: %.0fft", geoCoord.getAltitude() * METERS_TO_FEET);
displayLine = " Alt: " + String(geoCoord.getAltitude() * METERS_TO_FEET) + "ft"; } else {
snprintf(displayLine, sizeof(displayLine), " Alt: %.0fm", geoCoord.getAltitude());
}
display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine), displayLine); display->drawString(x, ((SCREEN_HEIGHT > 64) ? compactSecondLine : moreCompactSecondLine), displayLine);
// === Third Row: Latitude === // === Third Row: Latitude ===

View File

@ -11,6 +11,7 @@
#include "PowerFSM.h" // needed for button bypass #include "PowerFSM.h" // needed for button bypass
#include "SPILock.h" #include "SPILock.h"
#include "detect/ScanI2C.h" #include "detect/ScanI2C.h"
#include "graphics/Screen.h"
#include "graphics/SharedUIDisplay.h" #include "graphics/SharedUIDisplay.h"
#include "graphics/images.h" #include "graphics/images.h"
#include "input/ScanAndSelect.h" #include "input/ScanAndSelect.h"
@ -732,9 +733,7 @@ bool CannedMessageModule::handleSystemCommandInput(const InputEvent *event)
return false; return false;
// Block ALL input if an alert banner is active // Block ALL input if an alert banner is active
extern String alertBannerMessage; if (strlen(alertBannerMessage) > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil)) {
extern uint32_t alertBannerUntil;
if (alertBannerMessage.length() > 0 && (alertBannerUntil == 0 || millis() <= alertBannerUntil)) {
return true; return true;
} }
@ -1462,12 +1461,12 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
int xOffset = 0; int xOffset = 0;
int yOffset = row * (FONT_HEIGHT_SMALL - 4) + rowYOffset; int yOffset = row * (FONT_HEIGHT_SMALL - 4) + rowYOffset;
String entryText; char entryText[64];
// Draw Channels First // Draw Channels First
if (itemIndex < numActiveChannels) { if (itemIndex < numActiveChannels) {
uint8_t channelIndex = this->activeChannelIndices[itemIndex]; uint8_t channelIndex = this->activeChannelIndices[itemIndex];
entryText = String("@") + String(channels.getName(channelIndex)); snprintf(entryText, sizeof(entryText), "@%s", channels.getName(channelIndex));
} }
// Then Draw Nodes // Then Draw Nodes
else { else {
@ -1475,13 +1474,17 @@ void CannedMessageModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *st
if (nodeIndex >= 0 && nodeIndex < static_cast<int>(this->filteredNodes.size())) { if (nodeIndex >= 0 && nodeIndex < static_cast<int>(this->filteredNodes.size())) {
meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node; meshtastic_NodeInfoLite *node = this->filteredNodes[nodeIndex].node;
if (node) { if (node) {
entryText = node->is_favorite ? "* " + String(node->user.long_name) : String(node->user.long_name); 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") if (strlen(entryText) == 0 || strcmp(entryText, "Unknown") == 0)
entryText = "?"; strcpy(entryText, "?");
// === Highlight background (if selected) === // === Highlight background (if selected) ===
if (itemIndex == destIndex) { if (itemIndex == destIndex) {

View File

@ -341,7 +341,7 @@ ProcessMessage SerialModuleRadio::handleReceived(const meshtastic_MeshPacket &mp
serialPrint->write(p.payload.bytes, p.payload.size); serialPrint->write(p.payload.bytes, p.payload.size);
} else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) { } else if (moduleConfig.serial.mode == meshtastic_ModuleConfig_SerialConfig_Serial_Mode_TEXTMSG) {
meshtastic_NodeInfoLite *node = nodeDB->getMeshNode(getFrom(&mp)); 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->println();
serialPrint->printf("%s: %s", sender, p.payload.bytes); serialPrint->printf("%s: %s", sender, p.payload.bytes);
serialPrint->println(); serialPrint->println();
@ -410,8 +410,8 @@ uint32_t SerialModule::getBaudRate()
// Add this structure to help with parsing WindGust = 24.4 serial lines. // Add this structure to help with parsing WindGust = 24.4 serial lines.
struct ParsedLine { struct ParsedLine {
String name; char name[64];
String value; char value[128];
}; };
/** /**
@ -438,16 +438,30 @@ ParsedLine parseLine(const char *line)
strncpy(nameBuf, line, nameLen); strncpy(nameBuf, line, nameLen);
nameBuf[nameLen] = '\0'; nameBuf[nameLen] = '\0';
// Create trimmed name string // Trim whitespace from name
String name = String(nameBuf); char *nameStart = nameBuf;
name.trim(); while (*nameStart && isspace(*nameStart))
nameStart++;
char *nameEnd = nameStart + strlen(nameStart) - 1;
while (nameEnd > nameStart && isspace(*nameEnd))
*nameEnd-- = '\0';
// Extract value after equals sign // Copy trimmed name
String value = String(equals + 1); strncpy(result.name, nameStart, sizeof(result.name) - 1);
value.trim(); 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; return result;
} }
@ -517,16 +531,16 @@ void SerialModule::processWXSerial()
memcpy(line, &serialBytes[lineStart], lineEnd - lineStart); memcpy(line, &serialBytes[lineStart], lineEnd - lineStart);
ParsedLine parsed = parseLine(line); ParsedLine parsed = parseLine(line);
if (parsed.name.length() > 0) { if (strlen(parsed.name) > 0) {
if (parsed.name == "WindDir") { if (strcmp(parsed.name, "WindDir") == 0) {
strlcpy(windDir, parsed.value.c_str(), sizeof(windDir)); strlcpy(windDir, parsed.value, sizeof(windDir));
double radians = GeoCoord::toRadians(strtof(windDir, nullptr)); double radians = GeoCoord::toRadians(strtof(windDir, nullptr));
dir_sum_sin += sin(radians); dir_sum_sin += sin(radians);
dir_sum_cos += cos(radians); dir_sum_cos += cos(radians);
dirCount++; dirCount++;
gotwind = true; gotwind = true;
} else if (parsed.name == "WindSpeed") { } else if (strcmp(parsed.name, "WindSpeed") == 0) {
strlcpy(windVel, parsed.value.c_str(), sizeof(windVel)); strlcpy(windVel, parsed.value, sizeof(windVel));
float newv = strtof(windVel, nullptr); float newv = strtof(windVel, nullptr);
velSum += newv; velSum += newv;
velCount++; velCount++;
@ -534,28 +548,28 @@ void SerialModule::processWXSerial()
lull = newv; lull = newv;
} }
gotwind = true; gotwind = true;
} else if (parsed.name == "WindGust") { } else if (strcmp(parsed.name, "WindGust") == 0) {
strlcpy(windGust, parsed.value.c_str(), sizeof(windGust)); strlcpy(windGust, parsed.value, sizeof(windGust));
float newg = strtof(windGust, nullptr); float newg = strtof(windGust, nullptr);
if (newg > gust) { if (newg > gust) {
gust = newg; gust = newg;
} }
gotwind = true; gotwind = true;
} else if (parsed.name == "BatVoltage") { } else if (strcmp(parsed.name, "BatVoltage") == 0) {
strlcpy(batVoltage, parsed.value.c_str(), sizeof(batVoltage)); strlcpy(batVoltage, parsed.value, sizeof(batVoltage));
batVoltageF = strtof(batVoltage, nullptr); batVoltageF = strtof(batVoltage, nullptr);
break; // last possible data we want so break break; // last possible data we want so break
} else if (parsed.name == "CapVoltage") { } else if (strcmp(parsed.name, "CapVoltage") == 0) {
strlcpy(capVoltage, parsed.value.c_str(), sizeof(capVoltage)); strlcpy(capVoltage, parsed.value, sizeof(capVoltage));
capVoltageF = strtof(capVoltage, nullptr); capVoltageF = strtof(capVoltage, nullptr);
} else if (parsed.name == "GXTS04Temp" || parsed.name == "Temperature") { } else if (strcmp(parsed.name, "GXTS04Temp") == 0 || strcmp(parsed.name, "Temperature") == 0) {
strlcpy(temperature, parsed.value.c_str(), sizeof(temperature)); strlcpy(temperature, parsed.value, sizeof(temperature));
temperatureF = strtof(temperature, nullptr); temperatureF = strtof(temperature, nullptr);
} else if (parsed.name == "RainIntSum") { } else if (strcmp(parsed.name, "RainIntSum") == 0) {
strlcpy(rainStr, parsed.value.c_str(), sizeof(rainStr)); strlcpy(rainStr, parsed.value, sizeof(rainStr));
rainSum = int(strtof(rainStr, nullptr)); rainSum = int(strtof(rainStr, nullptr));
} else if (parsed.name == "Rain") { } else if (strcmp(parsed.name, "Rain") == 0) {
strlcpy(rainStr, parsed.value.c_str(), sizeof(rainStr)); strlcpy(rainStr, parsed.value, sizeof(rainStr));
rain = strtof(rainStr, nullptr); rain = strtof(rainStr, nullptr);
} }
} }

View File

@ -118,22 +118,31 @@ void HealthTelemetryModule::drawFrame(OLEDDisplay *display, OLEDDisplayUiState *
} }
// Display "Health From: ..." on its own // 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) { 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 // 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) { if (lastMeasurement.variant.health_metrics.has_heart_bpm) {
display->drawString(x, y += _fontHeight(FONT_SMALL), char heartStr[32];
"Heart Rate: " + String(lastMeasurement.variant.health_metrics.heart_bpm, 0) + " bpm"); 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) { if (lastMeasurement.variant.health_metrics.has_spO2) {
display->drawString(x, y += _fontHeight(FONT_SMALL), char spo2Str[32];
"spO2: " + String(lastMeasurement.variant.health_metrics.spO2, 0) + " %"); 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 "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 // Display current and voltage based on ...power_metrics.has_[channel/voltage/current]... flags
const auto &m = lastMeasurement.variant.power_metrics; const auto &m = lastMeasurement.variant.power_metrics;
int lineY = compactSecondLine; int lineY = compactSecondLine;
auto drawLine = [&](const char *label, float voltage, float current) { 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); lineY += _fontHeight(FONT_SMALL);
}; };

View File

@ -137,17 +137,17 @@ void BME680Sensor::updateState()
#endif #endif
} }
void BME680Sensor::checkStatus(String functionName) void BME680Sensor::checkStatus(const char *functionName)
{ {
if (bme680.status < BSEC_OK) 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) 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) 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) 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 #endif

View File

@ -34,7 +34,7 @@ class BME680Sensor : public TelemetrySensor
BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY}; BSEC_OUTPUT_SENSOR_HEAT_COMPENSATED_HUMIDITY};
void loadState(); void loadState();
void updateState(); void updateState();
void checkStatus(String functionName); void checkStatus(const char *functionName);
public: public:
BME680Sensor(); BME680Sensor();

View File

@ -109,14 +109,14 @@ class NimbleBluetoothServerCallback : public NimBLEServerCallbacks
display->drawString(x_offset + x, y_offset + y, "Enter this code"); display->drawString(x_offset + x, y_offset + y, "Enter this code");
display->setFont(FONT_LARGE); display->setFont(FONT_LARGE);
String displayPin(btPIN); char pin[8];
String pin = displayPin.substring(0, 3) + " " + displayPin.substring(3, 6); 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; 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->drawString(x_offset + x, y_offset + y, pin);
display->setFont(FONT_SMALL); display->setFont(FONT_SMALL);
String deviceName = "Name: "; char deviceName[64];
deviceName.concat(getDeviceName()); snprintf(deviceName, sizeof(deviceName), "Name: %s", getDeviceName());
y_offset = display->height() == 64 ? y_offset + FONT_HEIGHT_LARGE - 6 : y_offset + FONT_HEIGHT_LARGE + 5; 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); display->drawString(x_offset + x, y_offset + y, deviceName);
}); });

View File

@ -80,13 +80,13 @@ bool trySwitchToOTA()
return true; return true;
} }
String getVersion() const char *getVersion()
{ {
const esp_partition_t *part = getAppPartition(); const esp_partition_t *part = getAppPartition();
esp_app_desc_t app_desc; static esp_app_desc_t app_desc;
if (!getAppDesc(part, &app_desc)) if (!getAppDesc(part, &app_desc))
return String(); return "";
return String(app_desc.version); return app_desc.version;
} }
} // namespace WiFiOTA } // namespace WiFiOTA

View File

@ -12,7 +12,7 @@ bool isUpdated();
void recoverConfig(meshtastic_Config_NetworkConfig *network); void recoverConfig(meshtastic_Config_NetworkConfig *network);
void saveConfig(meshtastic_Config_NetworkConfig *network); void saveConfig(meshtastic_Config_NetworkConfig *network);
bool trySwitchToOTA(); bool trySwitchToOTA();
String getVersion(); const char *getVersion();
} // namespace WiFiOTA } // namespace WiFiOTA
#endif // WIFIOTA_H #endif // WIFIOTA_H