Skip to content

Commit 6208c24

Browse files
authored
BaseUI: Implementation of Status Message for Favorite and NodeList views (#9504)
* Implementation of Status Message * Change drawNodeInfo to drawFavoriteNode * Truncate overflow on Favorite frame * Set MAX_RECENT_STATUSMESSAGES to 5 to meet memory usage targets
1 parent 3a87e74 commit 6208c24

7 files changed

Lines changed: 124 additions & 9 deletions

File tree

platformio.ini

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ build_flags = -Wno-missing-field-initializers
5656
-DMESHTASTIC_EXCLUDE_POWERSTRESS=1 ; exclude power stress test module from main firmware
5757
-DMESHTASTIC_EXCLUDE_GENERIC_THREAD_MODULE=1
5858
-DMESHTASTIC_EXCLUDE_POWERMON=1
59-
-DMESHTASTIC_EXCLUDE_STATUS=1
6059
-D MAX_THREADS=40 ; As we've split modules, we have more threads to manage
6160
#-DBUILD_EPOCH=$UNIX_TIME ; set in platformio-custom.py now
6261
#-D OLED_PL=1

src/graphics/Screen.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,7 +1197,7 @@ void Screen::setFrames(FrameFocus focus)
11971197
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
11981198
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
11991199
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
1200-
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
1200+
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
12011201
}
12021202
}
12031203

@@ -1226,7 +1226,7 @@ void Screen::setFrames(FrameFocus focus)
12261226
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
12271227
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
12281228

1229-
prevFrame = -1; // Force drawNodeInfo to pick a new node (because our list just changed)
1229+
prevFrame = -1; // Force drawFavoriteNode to pick a new node (because our list just changed)
12301230

12311231
// Focus on a specific frame, in the frame set we just created
12321232
switch (focus) {

src/graphics/draw/NodeListRenderer.cpp

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
#include "CompassRenderer.h"
44
#include "NodeDB.h"
55
#include "NodeListRenderer.h"
6+
#if !MESHTASTIC_EXCLUDE_STATUS
7+
#include "modules/StatusMessageModule.h"
8+
#endif
69
#include "UIRenderer.h"
710
#include "gps/GeoCoord.h"
811
#include "gps/RTC.h" // for getTime() function
@@ -92,8 +95,41 @@ std::string getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
9295

9396
// 1) Choose target candidate (long vs short) only if present
9497
const char *raw = nullptr;
95-
if (node && node->has_user) {
96-
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
98+
99+
#if !MESHTASTIC_EXCLUDE_STATUS
100+
// If long-name mode is enabled, and we have a recent status for this node,
101+
// prefer "(short_name) statusText" as the raw candidate.
102+
std::string composedFromStatus;
103+
if (config.display.use_long_node_name && node && node->has_user && statusMessageModule) {
104+
const auto &recent = statusMessageModule->getRecentReceived();
105+
const StatusMessageModule::RecentStatus *found = nullptr;
106+
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
107+
if (it->fromNodeId == node->num && !it->statusText.empty()) {
108+
found = &(*it);
109+
break;
110+
}
111+
}
112+
113+
if (found) {
114+
const char *shortName = node->user.short_name;
115+
composedFromStatus.reserve(4 + (shortName ? std::strlen(shortName) : 0) + 1 + found->statusText.size());
116+
composedFromStatus += "(";
117+
if (shortName && *shortName) {
118+
composedFromStatus += shortName;
119+
}
120+
composedFromStatus += ") ";
121+
composedFromStatus += found->statusText;
122+
123+
raw = composedFromStatus.c_str(); // safe for now; we'll sanitize immediately into std::string
124+
}
125+
}
126+
#endif
127+
128+
// If we didn't compose from status, use normal long/short selection
129+
if (!raw) {
130+
if (node && node->has_user) {
131+
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
132+
}
97133
}
98134

99135
// 2) Preserve UTF-8 names so emotes can be detected and rendered.

src/graphics/draw/UIRenderer.cpp

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
#include "MeshService.h"
66
#include "NodeDB.h"
77
#include "NodeListRenderer.h"
8+
#if !MESHTASTIC_EXCLUDE_STATUS
9+
#include "modules/StatusMessageModule.h"
10+
#endif
811
#include "UIRenderer.h"
912
#include "airtime.h"
1013
#include "gps/GeoCoord.h"
@@ -290,7 +293,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
290293
// * Favorite Node Info *
291294
// **********************
292295
// cppcheck-suppress constParameterPointer; signature must match FrameCallback typedef from OLEDDisplayUi library
293-
void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
296+
void UIRenderer::drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y)
294297
{
295298
if (favoritedNodes.empty())
296299
return;
@@ -342,6 +345,57 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, i
342345
UIRenderer::drawStringWithEmotes(display, x, getTextPositions(display)[line++], username, FONT_HEIGHT_SMALL, 1, false);
343346
}
344347

348+
#if !MESHTASTIC_EXCLUDE_STATUS
349+
// === Optional: Last received StatusMessage line for this node ===
350+
// Display it directly under the username line (if we have one).
351+
if (statusMessageModule) {
352+
const auto &recent = statusMessageModule->getRecentReceived();
353+
const StatusMessageModule::RecentStatus *found = nullptr;
354+
355+
// Search newest-to-oldest
356+
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
357+
if (it->fromNodeId == node->num && !it->statusText.empty()) {
358+
found = &(*it);
359+
break;
360+
}
361+
}
362+
363+
if (found) {
364+
std::string statusLine = std::string(" Status: ") + found->statusText;
365+
{
366+
const int screenW = display->getWidth();
367+
const int ellipseW = display->getStringWidth("...");
368+
int w = display->getStringWidth(statusLine.c_str());
369+
370+
// Only do work if it overflows
371+
if (w > screenW) {
372+
bool truncated = false;
373+
if (ellipseW > screenW) {
374+
statusLine.clear();
375+
} else {
376+
while (!statusLine.empty()) {
377+
// remove one char (byte) at a time
378+
statusLine.pop_back();
379+
truncated = true;
380+
381+
// Measure candidate with ellipsis appended
382+
std::string candidate = statusLine + "...";
383+
if (display->getStringWidth(candidate.c_str()) <= screenW) {
384+
statusLine = std::move(candidate);
385+
break;
386+
}
387+
}
388+
if (statusLine.empty() && ellipseW <= screenW) {
389+
statusLine = "...";
390+
}
391+
}
392+
}
393+
}
394+
display->drawString(x, getTextPositions(display)[line++], statusLine.c_str());
395+
}
396+
}
397+
#endif
398+
345399
// === 2. Signal and Hops (combined on one line, if available) ===
346400
char signalHopsStr[32] = "";
347401
bool haveSignal = false;

src/graphics/draw/UIRenderer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class UIRenderer
5050
// Navigation bar overlay
5151
static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state);
5252

53-
static void drawNodeInfo(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
53+
static void drawFavoriteNode(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
5454

5555
static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
5656

src/modules/StatusMessageModule.cpp

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,23 @@ int32_t StatusMessageModule::runOnce()
2929
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
3030
{
3131
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
32-
meshtastic_StatusMessage incomingMessage;
32+
meshtastic_StatusMessage incomingMessage = meshtastic_StatusMessage_init_zero;
33+
3334
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
3435
&incomingMessage)) {
36+
3537
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
38+
39+
RecentStatus entry;
40+
entry.fromNodeId = mp.from;
41+
entry.statusText = incomingMessage.status;
42+
43+
recentReceived.push_back(std::move(entry));
44+
45+
// Keep only last MAX_RECENT_STATUSMESSAGES
46+
if (recentReceived.size() > MAX_RECENT_STATUSMESSAGES) {
47+
recentReceived.erase(recentReceived.begin()); // drop oldest
48+
}
3649
}
3750
}
3851
return ProcessMessage::CONTINUE;

src/modules/StatusMessageModule.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
#if !MESHTASTIC_EXCLUDE_STATUS
33
#include "SinglePortModule.h"
44
#include "configuration.h"
5+
#include <string>
6+
#include <vector>
57

68
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
79
{
8-
910
public:
1011
/** Constructor
1112
* name is for debugging output
@@ -19,16 +20,28 @@ class StatusMessageModule : public SinglePortModule, private concurrency::OSThre
1920
this->setInterval(1000 * 12 * 60 * 60);
2021
}
2122
// TODO: If we have a string, set the initial delay (15 minutes maybe)
23+
24+
// Keep vector from reallocating as we fill up to MAX_RECENT_STATUSMESSAGES
25+
recentReceived.reserve(MAX_RECENT_STATUSMESSAGES);
2226
}
2327

2428
virtual int32_t runOnce() override;
2529

30+
struct RecentStatus {
31+
uint32_t fromNodeId; // mp.from
32+
std::string statusText; // incomingMessage.status
33+
};
34+
35+
const std::vector<RecentStatus> &getRecentReceived() const { return recentReceived; }
36+
2637
protected:
2738
/** Called to handle a particular incoming message
2839
*/
2940
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
3041

3142
private:
43+
static constexpr size_t MAX_RECENT_STATUSMESSAGES = 5;
44+
std::vector<RecentStatus> recentReceived;
3245
};
3346

3447
extern StatusMessageModule *statusMessageModule;

0 commit comments

Comments
 (0)