Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/graphics/Screen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1175,7 +1175,7 @@ void Screen::setFrames(FrameFocus focus)
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
}
}

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

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

// Focus on a specific frame, in the frame set we just created
switch (focus) {
Expand Down
40 changes: 38 additions & 2 deletions src/graphics/draw/NodeListRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
#include "CompassRenderer.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
#include "UIRenderer.h"
#include "gps/GeoCoord.h"
#include "gps/RTC.h" // for getTime() function
Expand Down Expand Up @@ -90,8 +93,41 @@ const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,

// 1) Choose target candidate (long vs short) only if present
const char *raw = nullptr;
if (node && node->has_user) {
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;

#if !MESHTASTIC_EXCLUDE_STATUS
// If long-name mode is enabled, and we have a recent status for this node,
// prefer "(short_name) statusText" as the raw candidate.
std::string composedFromStatus;
if (config.display.use_long_node_name && node && node->has_user && statusMessageModule) {
const auto &recent = statusMessageModule->getRecentReceived();
const StatusMessageModule::RecentStatus *found = nullptr;
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
if (it->fromNodeId == node->num && !it->statusText.empty()) {
found = &(*it);
break;
}
}

if (found) {
const char *shortName = node->user.short_name;
composedFromStatus.reserve(4 + (shortName ? std::strlen(shortName) : 0) + 1 + found->statusText.size());
composedFromStatus += "(";
if (shortName && *shortName) {
composedFromStatus += shortName;
}
composedFromStatus += ") ";
composedFromStatus += found->statusText;

raw = composedFromStatus.c_str(); // safe for now; we'll sanitize immediately into std::string
}
}
#endif

// If we didn't compose from status, use normal long/short selection
if (!raw) {
if (node && node->has_user) {
raw = config.display.use_long_node_name ? node->user.long_name : node->user.short_name;
}
}

// 2) Sanitize (empty if raw is null/empty)
Expand Down
56 changes: 55 additions & 1 deletion src/graphics/draw/UIRenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
#include "MeshService.h"
#include "NodeDB.h"
#include "NodeListRenderer.h"
#if !MESHTASTIC_EXCLUDE_STATUS
#include "modules/StatusMessageModule.h"
#endif
#include "UIRenderer.h"
#include "airtime.h"
#include "gps/GeoCoord.h"
Expand Down Expand Up @@ -288,7 +291,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
// **********************
// * Favorite Node Info *
// **********************
void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
void UIRenderer::drawFavoriteNode(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y)
{
if (favoritedNodes.empty())
return;
Expand Down Expand Up @@ -342,6 +345,57 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
display->drawString(x, getTextPositions(display)[line++], usernameStr.c_str());
}

#if !MESHTASTIC_EXCLUDE_STATUS
// === Optional: Last received StatusMessage line for this node ===
// Display it directly under the username line (if we have one).
if (statusMessageModule) {
const auto &recent = statusMessageModule->getRecentReceived();
const StatusMessageModule::RecentStatus *found = nullptr;

// Search newest-to-oldest
for (auto it = recent.rbegin(); it != recent.rend(); ++it) {
if (it->fromNodeId == node->num && !it->statusText.empty()) {
found = &(*it);
break;
}
}

if (found) {
std::string statusLine = std::string(" Status: ") + found->statusText;
{
const int screenW = display->getWidth();
const int ellipseW = display->getStringWidth("...");
int w = display->getStringWidth(statusLine.c_str());

// Only do work if it overflows
if (w > screenW) {
bool truncated = false;
if (ellipseW > screenW) {
statusLine.clear();
} else {
while (!statusLine.empty()) {
// remove one char (byte) at a time
statusLine.pop_back();
truncated = true;

// Measure candidate with ellipsis appended
std::string candidate = statusLine + "...";
if (display->getStringWidth(candidate.c_str()) <= screenW) {
statusLine = std::move(candidate);
break;
}
}
if (statusLine.empty() && ellipseW <= screenW) {
statusLine = "...";
}
}
}
}
display->drawString(x, getTextPositions(display)[line++], statusLine.c_str());
}
}
#endif

// === 2. Signal and Hops (combined on one line, if available) ===
char signalHopsStr[32] = "";
bool haveSignal = false;
Expand Down
2 changes: 1 addition & 1 deletion src/graphics/draw/UIRenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ class UIRenderer
// Navigation bar overlay
static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state);

static void drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y);
static void drawFavoriteNode(OLEDDisplay *display, const OLEDDisplayUiState *state, int16_t x, int16_t y);

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

Expand Down
15 changes: 14 additions & 1 deletion src/modules/StatusMessageModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,23 @@ int32_t StatusMessageModule::runOnce()
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
{
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
meshtastic_StatusMessage incomingMessage;
meshtastic_StatusMessage incomingMessage = meshtastic_StatusMessage_init_zero;

if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
&incomingMessage)) {

LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);

RecentStatus entry;
entry.fromNodeId = mp.from;
entry.statusText = incomingMessage.status;

recentReceived.push_back(std::move(entry));

// Keep only last MAX_RECENT_STATUSMESSAGES
if (recentReceived.size() > MAX_RECENT_STATUSMESSAGES) {
recentReceived.erase(recentReceived.begin()); // drop oldest
}
}
}
return ProcessMessage::CONTINUE;
Expand Down
15 changes: 14 additions & 1 deletion src/modules/StatusMessageModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
#if !MESHTASTIC_EXCLUDE_STATUS
#include "SinglePortModule.h"
#include "configuration.h"
#include <string>
#include <vector>

class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
{

public:
/** Constructor
* name is for debugging output
Expand All @@ -19,16 +20,28 @@ class StatusMessageModule : public SinglePortModule, private concurrency::OSThre
this->setInterval(1000 * 12 * 60 * 60);
}
// TODO: If we have a string, set the initial delay (15 minutes maybe)

// Keep vector from reallocating as we fill up to MAX_RECENT_STATUSMESSAGES
recentReceived.reserve(MAX_RECENT_STATUSMESSAGES);
}

virtual int32_t runOnce() override;

struct RecentStatus {
uint32_t fromNodeId; // mp.from
std::string statusText; // incomingMessage.status
};

const std::vector<RecentStatus> &getRecentReceived() const { return recentReceived; }

protected:
/** Called to handle a particular incoming message
*/
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;

private:
static constexpr size_t MAX_RECENT_STATUSMESSAGES = 5;
std::vector<RecentStatus> recentReceived;
};

extern StatusMessageModule *statusMessageModule;
Expand Down