mirror of
https://github.com/meshtastic/firmware.git
synced 2026-02-03 15:42:02 +00:00
Compare commits
9 Commits
agc-reset
...
baseui_sta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0d57a49b51 | ||
|
|
d8a0b6a737 | ||
|
|
0703e0e6d7 | ||
|
|
f514bc230b | ||
|
|
697dd2b5b2 | ||
|
|
0b8b757fb0 | ||
|
|
62f897eab3 | ||
|
|
523906d031 | ||
|
|
78f29c0f87 |
@@ -459,6 +459,8 @@ class AnalogBatteryLevel : public HasBatteryLevel
|
|||||||
}
|
}
|
||||||
// if it's not HIGH - check the battery
|
// if it's not HIGH - check the battery
|
||||||
#endif
|
#endif
|
||||||
|
// If we have an EXT_PWR_DETECT pin and it indicates no external power, believe it.
|
||||||
|
return false;
|
||||||
|
|
||||||
// technically speaking this should work for all(?) NRF52 boards
|
// technically speaking this should work for all(?) NRF52 boards
|
||||||
// but needs testing across multiple devices. NRF52 USB would not even work if
|
// but needs testing across multiple devices. NRF52 USB would not even work if
|
||||||
|
|||||||
@@ -1175,7 +1175,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
for (size_t i = 0; i < nodeDB->getNumMeshNodes(); i++) {
|
||||||
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
const meshtastic_NodeInfoLite *n = nodeDB->getMeshNodeByIndex(i);
|
||||||
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
if (n && n->num != nodeDB->getNodeNum() && n->is_favorite) {
|
||||||
favoriteFrames.push_back(graphics::UIRenderer::drawNodeInfo);
|
favoriteFrames.push_back(graphics::UIRenderer::drawFavoriteNode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1204,7 +1204,7 @@ void Screen::setFrames(FrameFocus focus)
|
|||||||
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
static OverlayCallback overlays[] = {graphics::UIRenderer::drawNavigationBar, NotificationRenderer::drawBannercallback};
|
||||||
ui->setOverlays(overlays, sizeof(overlays) / sizeof(overlays[0]));
|
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
|
// Focus on a specific frame, in the frame set we just created
|
||||||
switch (focus) {
|
switch (focus) {
|
||||||
|
|||||||
@@ -3,6 +3,9 @@
|
|||||||
#include "CompassRenderer.h"
|
#include "CompassRenderer.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "NodeListRenderer.h"
|
#include "NodeListRenderer.h"
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
#include "modules/StatusMessageModule.h"
|
||||||
|
#endif
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
#include "gps/GeoCoord.h"
|
#include "gps/GeoCoord.h"
|
||||||
#include "gps/RTC.h" // for getTime() function
|
#include "gps/RTC.h" // for getTime() function
|
||||||
@@ -90,8 +93,41 @@ const char *getSafeNodeName(OLEDDisplay *display, meshtastic_NodeInfoLite *node,
|
|||||||
|
|
||||||
// 1) Choose target candidate (long vs short) only if present
|
// 1) Choose target candidate (long vs short) only if present
|
||||||
const char *raw = nullptr;
|
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)
|
// 2) Sanitize (empty if raw is null/empty)
|
||||||
|
|||||||
@@ -4,6 +4,9 @@
|
|||||||
#include "GPSStatus.h"
|
#include "GPSStatus.h"
|
||||||
#include "NodeDB.h"
|
#include "NodeDB.h"
|
||||||
#include "NodeListRenderer.h"
|
#include "NodeListRenderer.h"
|
||||||
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
|
#include "modules/StatusMessageModule.h"
|
||||||
|
#endif
|
||||||
#include "UIRenderer.h"
|
#include "UIRenderer.h"
|
||||||
#include "airtime.h"
|
#include "airtime.h"
|
||||||
#include "gps/GeoCoord.h"
|
#include "gps/GeoCoord.h"
|
||||||
@@ -287,7 +290,7 @@ void UIRenderer::drawNodes(OLEDDisplay *display, int16_t x, int16_t y, const mes
|
|||||||
// **********************
|
// **********************
|
||||||
// * Favorite Node Info *
|
// * 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())
|
if (favoritedNodes.empty())
|
||||||
return;
|
return;
|
||||||
@@ -341,6 +344,57 @@ void UIRenderer::drawNodeInfo(OLEDDisplay *display, const OLEDDisplayUiState *st
|
|||||||
display->drawString(x, getTextPositions(display)[line++], usernameStr.c_str());
|
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) ===
|
// === 2. Signal and Hops (combined on one line, if available) ===
|
||||||
// If both are present: "Sig: 97% [2hops]"
|
// If both are present: "Sig: 97% [2hops]"
|
||||||
// If only one: show only that one
|
// If only one: show only that one
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ class UIRenderer
|
|||||||
// Navigation bar overlay
|
// Navigation bar overlay
|
||||||
static void drawNavigationBar(OLEDDisplay *display, OLEDDisplayUiState *state);
|
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);
|
static void drawDeviceFocused(OLEDDisplay *display, OLEDDisplayUiState *state, int16_t x, int16_t y);
|
||||||
|
|
||||||
|
|||||||
@@ -2223,8 +2223,8 @@ bool NodeDB::restorePreferences(meshtastic_AdminMessage_BackupLocation location,
|
|||||||
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
} else if (location == meshtastic_AdminMessage_BackupLocation_SD) {
|
||||||
// TODO: After more mainline SD card support
|
// TODO: After more mainline SD card support
|
||||||
}
|
}
|
||||||
return success;
|
|
||||||
#endif
|
#endif
|
||||||
|
return success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Record an error that should be reported via analytics
|
/// Record an error that should be reported via analytics
|
||||||
|
|||||||
@@ -29,10 +29,23 @@ int32_t StatusMessageModule::runOnce()
|
|||||||
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
ProcessMessage StatusMessageModule::handleReceived(const meshtastic_MeshPacket &mp)
|
||||||
{
|
{
|
||||||
if (mp.which_payload_variant == meshtastic_MeshPacket_decoded_tag) {
|
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,
|
if (pb_decode_from_bytes(mp.decoded.payload.bytes, mp.decoded.payload.size, meshtastic_StatusMessage_fields,
|
||||||
&incomingMessage)) {
|
&incomingMessage)) {
|
||||||
|
|
||||||
LOG_INFO("Received a NodeStatus message %s", incomingMessage.status);
|
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;
|
return ProcessMessage::CONTINUE;
|
||||||
|
|||||||
@@ -2,10 +2,11 @@
|
|||||||
#if !MESHTASTIC_EXCLUDE_STATUS
|
#if !MESHTASTIC_EXCLUDE_STATUS
|
||||||
#include "SinglePortModule.h"
|
#include "SinglePortModule.h"
|
||||||
#include "configuration.h"
|
#include "configuration.h"
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
class StatusMessageModule : public SinglePortModule, private concurrency::OSThread
|
||||||
{
|
{
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/** Constructor
|
/** Constructor
|
||||||
* name is for debugging output
|
* name is for debugging output
|
||||||
@@ -19,16 +20,28 @@ class StatusMessageModule : public SinglePortModule, private concurrency::OSThre
|
|||||||
this->setInterval(1000 * 12 * 60 * 60);
|
this->setInterval(1000 * 12 * 60 * 60);
|
||||||
}
|
}
|
||||||
// TODO: If we have a string, set the initial delay (15 minutes maybe)
|
// 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;
|
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:
|
protected:
|
||||||
/** Called to handle a particular incoming message
|
/** Called to handle a particular incoming message
|
||||||
*/
|
*/
|
||||||
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
virtual ProcessMessage handleReceived(const meshtastic_MeshPacket &mp) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
static constexpr size_t MAX_RECENT_STATUSMESSAGES = 5;
|
||||||
|
std::vector<RecentStatus> recentReceived;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern StatusMessageModule *statusMessageModule;
|
extern StatusMessageModule *statusMessageModule;
|
||||||
|
|||||||
Reference in New Issue
Block a user