Add on-device Rx Log packet sniffer and RX packet counter

RX packet counter: shows total received packets (flood + direct) on the
radio details page; RAM-only, resets on boot and on any SF/freq/BW change.

Rx Log: app-style packet sniffer opened from Settings > "Rx Log >>".
Captures the last 100 received packets pre-filter via Dispatcher::logRx
into a PSRAM ring, with decoded "sender: message" lines matched by packet
hash for decryptable channels. Each entry shows route/type, time, size,
hash, path, channel or From/To, SNR. W/S scroll, Q back to Settings;
T5S3 swipe-scroll rides the same path.

Files: MyMesh.h/.cpp, RxLogScreen.h (new), UITask.h/.cpp,
SettingsScreen.h, main.cpp.
This commit is contained in:
pelgraine
2026-06-07 05:14:16 +10:00
parent 353048aab3
commit 38656f9d1d
8 changed files with 363 additions and 1 deletions
+49
View File
@@ -325,6 +325,33 @@ void MyMesh::logRxRaw(float snr, float rssi, const uint8_t raw[], int len) {
}
}
void MyMesh::logRx(mesh::Packet* pkt, int len, float score) {
if (_rxlog == nullptr) return;
RxLogEntry& e = _rxlog[_rxlog_head];
memset(&e, 0, sizeof(e));
e.timestamp = getRTCClock()->getCurrentTime();
e.header = pkt->header;
e.path_len = pkt->path_len;
e.size = (uint16_t)len;
e.snr = pkt->_snr;
e.payload0 = (pkt->payload_len > 0) ? pkt->payload[0] : 0;
e.payload1 = (pkt->payload_len > 1) ? pkt->payload[1] : 0;
pkt->calculatePacketHash(e.hash);
uint16_t pbl = pkt->getPathByteLen();
if (pbl > MAX_PATH_SIZE) pbl = MAX_PATH_SIZE;
memcpy(e.path, pkt->path, pbl);
// channel_name / text stay empty here; attached by onChannelMessageRecv() for
// decryptable channel messages, matched on packet hash.
_rxlog_head = (_rxlog_head + 1) % RXLOG_SIZE;
if (_rxlog_count < RXLOG_SIZE) _rxlog_count++;
}
const RxLogEntry* MyMesh::getRxLogEntry(int idx) const {
if (_rxlog == nullptr || idx < 0 || idx >= _rxlog_count) return nullptr;
int oldest = (_rxlog_head - _rxlog_count + RXLOG_SIZE) % RXLOG_SIZE;
return &_rxlog[(oldest + idx) % RXLOG_SIZE];
}
bool MyMesh::isAutoAddEnabled() const {
return (_prefs.manual_add_contacts & 1) == 0;
}
@@ -844,6 +871,24 @@ void MyMesh::onChannelMessageRecv(const mesh::GroupChannel &channel, mesh::Packe
_serial->writeFrame(frame, 1);
}
// Rx Log: attach the decoded text + channel name to the matching captured
// entry, found by packet hash. logRx() ran first and stored the header fields.
if (_rxlog != nullptr) {
uint8_t h[MAX_HASH_SIZE];
pkt->calculatePacketHash(h);
ChannelDetails cd;
const char* cname = getChannel(channel_idx, cd) ? cd.name : "";
int oldest = (_rxlog_head - _rxlog_count + RXLOG_SIZE) % RXLOG_SIZE;
for (int n = _rxlog_count - 1; n >= 0; n--) {
RxLogEntry& e = _rxlog[(oldest + n) % RXLOG_SIZE];
if (e.text[0] == 0 && memcmp(e.hash, h, MAX_HASH_SIZE) == 0) {
StrHelper::strncpy(e.text, text, sizeof(e.text));
StrHelper::strncpy(e.channel_name, cname, sizeof(e.channel_name));
break;
}
}
}
#ifdef DISPLAY_CLASS
// Get the channel name from the channel index
const char *channel_name = "Unknown";
@@ -1451,6 +1496,9 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
sign_data = NULL;
dirty_contacts_expiry = 0;
advert_paths = nullptr; // PSRAM-allocated in begin()
_rxlog = nullptr; // PSRAM-allocated in begin()
_rxlog_head = 0;
_rxlog_count = 0;
memset(send_scope.key, 0, sizeof(send_scope.key));
memset(_sent_track, 0, sizeof(_sent_track));
_sent_track_idx = 0;
@@ -1478,6 +1526,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
void MyMesh::begin(bool has_display) {
advert_paths = (AdvertPath*)ps_calloc(ADVERT_PATH_TABLE_SIZE, sizeof(AdvertPath));
_rxlog = (RxLogEntry*)ps_calloc(RXLOG_SIZE, sizeof(RxLogEntry));
BaseChatMesh::begin();
if (!_store->loadMainIdentity(self_id)) {
+42
View File
@@ -104,6 +104,29 @@ struct DiscoveredNode {
#define MECK_CH_PREFIX "[MECK:CH]"
#define MECK_CH_PREFIX_LEN 9
// Rx Log -- on-device packet sniffer ring buffer that mirrors the MeshCore app's
// Rx Log. logRx() captures every received packet (including foreign relays, since
// it fires pre-filter) into header fields; for decryptable channel messages the
// decoded "name: msg" text and channel name are attached later by
// onChannelMessageRecv(), matched on packet hash. RAM only, lost on reboot.
#define RXLOG_SIZE 100
#define RXLOG_TEXT_LEN 64
#define RXLOG_CHNAME_LEN 16
struct RxLogEntry {
uint32_t timestamp; // local RTC at receive
uint8_t header; // route type + payload type + ver (Packet::header)
uint8_t path_len; // hop count (low 6 bits) + bytes-per-hop mode (high 2)
uint16_t size; // wire length (getRawLength)
int8_t snr; // SNR x4 (snr / 4.0 = dB)
uint8_t payload0; // first payload byte: channel hash, or dest hash (addressed)
uint8_t payload1; // second payload byte: src hash (addressed types only)
uint8_t hash[MAX_HASH_SIZE]; // packet hash
uint8_t path[MAX_PATH_SIZE]; // hop hashes (layout per path_len)
char channel_name[RXLOG_CHNAME_LEN]; // decrypted channel name (no '#'); empty if undecoded
char text[RXLOG_TEXT_LEN]; // decoded "name: msg"; empty if undecoded
};
class MyMesh : public BaseChatMesh, public DataStoreHost {
public:
MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMeshTables &tables, DataStore& store, AbstractUITask* ui=NULL);
@@ -115,6 +138,18 @@ public:
NodePrefs *getNodePrefs();
uint32_t getBLEPin();
// RX packet counter for the radio details page. Returns the number of packets
// (flood + direct) received since boot, less a baseline. resetRxPacketCount()
// snaps the baseline to the current total so the displayed count can be zeroed
// when radio params change. RAM only; reset on boot via Dispatcher::begin().
uint32_t getRxPacketCount() const { return (getNumRecvFlood() + getNumRecvDirect()) - _rx_count_baseline; }
void resetRxPacketCount() { _rx_count_baseline = getNumRecvFlood() + getNumRecvDirect(); }
// Rx Log accessors for the Rx Log screen. Entries are ordered oldest..newest.
int getRxLogCount() const { return _rxlog_count; }
const RxLogEntry* getRxLogEntry(int idx) const; // idx 0 = oldest; nullptr if out of range
void clearRxLog() { _rxlog_head = 0; _rxlog_count = 0; }
void loop();
void handleCmdFrame(size_t len);
bool advert();
@@ -210,6 +245,7 @@ protected:
void sendFloodScoped(const TransportKey& scope, mesh::Packet* pkt, uint32_t delay_millis=0);
void logRxRaw(float snr, float rssi, const uint8_t raw[], int len) override;
void logRx(mesh::Packet* pkt, int len, float score) override;
bool isAutoAddEnabled() const override;
bool shouldAutoAddContactType(uint8_t type) const override;
bool shouldOverwriteWhenFull() const override;
@@ -291,6 +327,7 @@ private:
mutable bool _forceNextImport = false;
bool _deferSaves = false;
unsigned long _lastUserInput = 0; // millis() of last keypress -- defer saves until idle
uint32_t _rx_count_baseline = 0; // baseline for RX packet counter (radio page); RAM only
uint32_t pending_login;
uint32_t pending_status;
uint32_t pending_telemetry, pending_discovery; // pending _TELEMETRY_REQ
@@ -351,6 +388,11 @@ private:
#endif
AdvertPath* advert_paths; // PSRAM-allocated in begin(), size = ADVERT_PATH_TABLE_SIZE
// Rx Log ring buffer (PSRAM-allocated in begin(), size RXLOG_SIZE). RAM only.
RxLogEntry* _rxlog;
int _rxlog_head; // index where the next entry will be written
int _rxlog_count; // number of valid entries (<= RXLOG_SIZE)
// Sent message repeat tracking
#define SENT_TRACK_SIZE 4
#define SENT_FINGERPRINT_SIZE 12
+14
View File
@@ -4797,6 +4797,11 @@ void handleKeyboardInput() {
}
}
#endif
// Check for Rx Log open request from the settings screen
if (settings->isRxLogRequested()) {
settings->clearRxLogRequest();
ui_task.gotoRxLogScreen();
}
// Check for channel share request from the settings screen
if (settings->isShareRequested()) {
int contactIdx = settings->getShareContactIdx();
@@ -5337,6 +5342,7 @@ void handleKeyboardInput() {
#ifdef MECK_AUDIO_VARIANT
|| ui_task.isOnAlarmScreen()
#endif
|| ui_task.isOnRxLogScreen()
) {
ui_task.injectKey('S');
}
@@ -5357,6 +5363,7 @@ void handleKeyboardInput() {
|| ui_task.isOnAlarmScreen()
#endif
|| ui_task.isHomeOnShutdownPage()
|| ui_task.isOnRxLogScreen()
) {
ui_task.injectKey('s'); // Pass directly for scrolling
} else {
@@ -5379,6 +5386,7 @@ void handleKeyboardInput() {
#ifdef MECK_AUDIO_VARIANT
|| ui_task.isOnAlarmScreen()
#endif
|| ui_task.isOnRxLogScreen()
) {
ui_task.injectKey('W');
}
@@ -5398,6 +5406,7 @@ void handleKeyboardInput() {
#ifdef MECK_AUDIO_VARIANT
|| ui_task.isOnAlarmScreen()
#endif
|| ui_task.isOnRxLogScreen()
) {
ui_task.injectKey('w'); // Pass directly for scrolling
} else {
@@ -5758,6 +5767,11 @@ void handleKeyboardInput() {
ui_task.gotoContactsScreen();
break;
}
// Rx Log screen: Q goes back to settings (screen handles it)
if (ui_task.isOnRxLogScreen()) {
ui_task.injectKey('q');
break;
}
// Path editor: Q goes back to contacts (discards unsaved changes)
if (ui_task.isOnPathEditor()) {
Serial.println("Nav: PathEditor -> Contacts");
@@ -84,6 +84,8 @@ static const AdminCmdDef CMD_SET_CONFIG[] = {
{ "Set AF", "set af ", "Airtime factor:", CMDF_PARAM },
{ "Set Repeat", "set repeat ", "on/off:", CMDF_PARAM },
{ "Set Flood Max", "set flood.max ", "Max hops (0-64):", CMDF_PARAM },
{ "Set Flood Max Unscoped", "set flood.max.unscoped ", "Max hops (64=off):", CMDF_PARAM },
{ "Set Flood Adv Max", "set flood.max.advert ", "Max hops (def 8):", CMDF_PARAM },
{ "Set RX Delay", "set rxdelay ", "Base (0=off):", CMDF_PARAM },
{ "Set TX Delay", "set txdelay ", "Factor:", CMDF_PARAM },
{ "Set Direct TX Delay", "set direct.txdelay ", "Factor:", CMDF_PARAM },
@@ -99,7 +101,7 @@ static const AdminCmdDef CMD_SET_CONFIG[] = {
{ "Temp Radio", "tempradio ", "freq,bw,sf,cr,mins:", CMDF_PARAM },
{ "Change Admin Pwd", "password ", "New password:", CMDF_PARAM | CMDF_CONFIRM },
};
#define CMD_SET_CONFIG_COUNT 19
#define CMD_SET_CONFIG_COUNT 21
// --- Power ---
static const AdminCmdDef CMD_POWER[] = {
@@ -0,0 +1,216 @@
#pragma once
#include <helpers/ui/UIScreen.h>
#include <helpers/ui/DisplayDriver.h>
#include <MeshCore.h>
class UITask; // forward decl -- used only to navigate back to Settings
extern MyMesh the_mesh;
// ==========================================================================
// Rx Log Screen -- on-device packet sniffer view, mirrors the MeshCore app's
// Rx Log. Reads the capture ring in MyMesh (filled by logRx()) and renders
// each received packet as an app-style block: route + payload type, time,
// size, hash, path, channel hash/name or From/To, the decoded line (for
// decryptable channels), and SNR. Entries are shown newest-first; W/S scroll
// by entry, Q returns to Settings (where the screen is opened from).
// ==========================================================================
class RxLogScreen : public UIScreen {
UITask* _task;
mesh::RTCClock* _rtc;
int _scrollPos; // display index of the top visible entry (0 = newest)
static const char* typeName(uint8_t t) {
switch (t) {
case PAYLOAD_TYPE_REQ: return "REQUEST";
case PAYLOAD_TYPE_RESPONSE: return "RESPONSE";
case PAYLOAD_TYPE_TXT_MSG: return "TEXT";
case PAYLOAD_TYPE_ACK: return "ACK";
case PAYLOAD_TYPE_ADVERT: return "ADVERT";
case PAYLOAD_TYPE_GRP_TXT: return "GROUP_TEXT";
case PAYLOAD_TYPE_GRP_DATA: return "GRP_DATA";
case PAYLOAD_TYPE_ANON_REQ: return "ANON_REQ";
case PAYLOAD_TYPE_PATH: return "PATH";
case PAYLOAD_TYPE_TRACE: return "TRACE";
case PAYLOAD_TYPE_MULTIPART: return "MULTIPART";
case PAYLOAD_TYPE_CONTROL: return "CONTROL";
case PAYLOAD_TYPE_RAW_CUSTOM: return "RAW";
default: return "?";
}
}
// Render one entry block starting at y; returns the y after the block.
int renderEntry(DisplayDriver& display, const RxLogEntry& e, int y, int lineH, int maxY) {
uint8_t route = e.header & 0x03; // PH_ROUTE_MASK
bool flood = (route == 0x00 || route == 0x01); // TRANSPORT_FLOOD or FLOOD
uint8_t ptype = (e.header >> 2) & 0x0F; // PH_TYPE_SHIFT / PH_TYPE_MASK
display.setColor(DisplayDriver::LIGHT);
// Line 1: route + payload type (left), SNR (right)
char l1[40];
snprintf(l1, sizeof(l1), "%s %s", flood ? "FLOOD" : "DIRECT", typeName(ptype));
display.setCursor(0, y);
display.print(l1);
char snrbuf[16];
snprintf(snrbuf, sizeof(snrbuf), "%.2fdB", e.snr / 4.0f);
display.setCursor(display.width() - display.getTextWidth(snrbuf) - 2, y);
display.print(snrbuf);
y += lineH;
if (y + lineH > maxY) return y;
// Line 2: time (HH:MM:SS, device UTC offset) + size
int32_t local = (int32_t)e.timestamp + ((int32_t)the_mesh.getNodePrefs()->utc_offset_hours * 3600);
int hrs = (local / 3600) % 24;
int mins = (local / 60) % 60;
int secs = local % 60;
if (hrs < 0) hrs += 24;
char l2[40];
snprintf(l2, sizeof(l2), "%02d:%02d:%02d %u bytes", hrs, mins, secs, (unsigned)e.size);
display.setCursor(0, y);
display.print(l2);
y += lineH;
if (y + lineH > maxY) return y;
// Line 3: packet hash
char hashbuf[2 * MAX_HASH_SIZE + 8];
int p = snprintf(hashbuf, sizeof(hashbuf), "Hash: ");
for (int i = 0; i < MAX_HASH_SIZE && p < (int)sizeof(hashbuf) - 3; i++) {
p += snprintf(hashbuf + p, sizeof(hashbuf) - p, "%02X", e.hash[i]);
}
display.setCursor(0, y);
display.print(hashbuf);
y += lineH;
if (y + lineH > maxY) return y;
// Line 4: path (hop count + hop hashes, per the bytes-per-hop mode)
uint8_t hops = e.path_len & 63;
uint8_t bph = (e.path_len >> 6) + 1;
char pathbuf[96];
int q = snprintf(pathbuf, sizeof(pathbuf), "Path: %d hops", hops);
if (hops > 0) {
q += snprintf(pathbuf + q, sizeof(pathbuf) - q, " [");
for (int h = 0; h < hops && q < (int)sizeof(pathbuf) - 8; h++) {
for (int b = 0; b < bph; b++) {
q += snprintf(pathbuf + q, sizeof(pathbuf) - q, "%02x", e.path[h * bph + b]);
}
if (h < hops - 1) q += snprintf(pathbuf + q, sizeof(pathbuf) - q, ",");
}
q += snprintf(pathbuf + q, sizeof(pathbuf) - q, "]");
}
display.drawTextEllipsized(0, y, display.width(), pathbuf);
y += lineH;
if (y + lineH > maxY) return y;
// Line 5: channel hash/name (group) OR From/To (addressed)
char l5[48];
bool haveL5 = false;
if (ptype == PAYLOAD_TYPE_GRP_TXT || ptype == PAYLOAD_TYPE_GRP_DATA) {
if (e.channel_name[0]) snprintf(l5, sizeof(l5), "Ch %02x #%s", e.payload0, e.channel_name);
else snprintf(l5, sizeof(l5), "Ch %02x", e.payload0);
haveL5 = true;
} else if (ptype == PAYLOAD_TYPE_REQ || ptype == PAYLOAD_TYPE_RESPONSE
|| ptype == PAYLOAD_TYPE_TXT_MSG || ptype == PAYLOAD_TYPE_PATH) {
snprintf(l5, sizeof(l5), "From %02x To %02x", e.payload1, e.payload0);
haveL5 = true;
}
if (haveL5) {
display.drawTextEllipsized(0, y, display.width(), l5);
y += lineH;
if (y + lineH > maxY) return y;
}
// Line 6: decoded "sender: message" (only present for decryptable channels)
if (e.text[0]) {
char tb[RXLOG_TEXT_LEN + 4];
display.translateUTF8ToBlocks(tb, e.text, sizeof(tb));
display.drawTextEllipsized(0, y, display.width(), tb);
y += lineH;
}
return y;
}
public:
RxLogScreen(UITask* task, mesh::RTCClock* rtc)
: _task(task), _rtc(rtc), _scrollPos(0) {}
void resetScroll() { _scrollPos = 0; }
int render(DisplayDriver& display) override {
int count = the_mesh.getRxLogCount();
if (_scrollPos < 0) _scrollPos = 0;
if (_scrollPos > count - 1) _scrollPos = (count > 0) ? count - 1 : 0;
// === Header ===
display.setTextSize(1);
display.setColor(DisplayDriver::GREEN);
display.setCursor(0, 0);
char hdr[32];
snprintf(hdr, sizeof(hdr), "Rx Log: %d pkts", count);
display.print(hdr);
display.drawRect(0, 11, display.width(), 1);
int headerHeight = 14;
int footerHeight = 14;
int maxY = display.height() - footerHeight;
int y = headerHeight;
if (count == 0) {
display.setColor(DisplayDriver::LIGHT);
display.setCursor(4, 28);
display.print("No packets received yet");
display.setCursor(4, 38);
display.print("Packets appear as they arrive");
} else {
display.setTextSize(the_mesh.getNodePrefs()->smallTextSize());
int lineH = the_mesh.getNodePrefs()->smallLineH();
// Render blocks newest-first, starting at display index _scrollPos,
// until the screen is full.
for (int d = _scrollPos; d < count && y + lineH <= maxY; d++) {
const RxLogEntry* e = the_mesh.getRxLogEntry(count - 1 - d);
if (!e) break;
y = renderEntry(display, *e, y, lineH, maxY);
y += 3; // gap between entry blocks
}
}
display.setTextSize(1);
// === Footer ===
int footerY = display.height() - 12;
display.drawRect(0, footerY - 2, display.width(), 1);
display.setColor(DisplayDriver::YELLOW);
display.setCursor(0, footerY);
#if defined(LilyGo_T5S3_EPaper_Pro)
display.print("Swipe:Scroll");
#else
display.print("Q:Bk W/S:Scroll");
#endif
return 5000; // refresh every 5s to pick up newly received packets
}
bool handleInput(char c) override {
int count = the_mesh.getRxLogCount();
// Scroll up (toward newest)
if (c == 'w' || c == 'W' || c == 0xF2) {
if (_scrollPos > 0) { _scrollPos--; return true; }
return false;
}
// Scroll down (toward oldest)
if (c == 's' || c == 'S' || c == 0xF1) {
if (_scrollPos < count - 1) { _scrollPos++; return true; }
return false;
}
// Back to Settings
if (c == 'q' || c == 'Q' || c == 0x1B) {
if (_task) _task->gotoSettingsScreen();
return true;
}
return false;
}
};
@@ -185,6 +185,7 @@ enum SettingsRowType : uint8_t {
ROW_EXPORT_AUTOADD, // Checkbox: include auto-add preferences (sub-item of contacts)
ROW_EXPORT_NOW, // ">> Export Now" action trigger
#endif
ROW_RXLOG, // Rx Log packet sniffer (opens RxLogScreen)
ROW_INFO_HEADER, // "--- Info ---" separator
#ifdef MECK_OTA_UPDATE
ROW_OTA_TOOLS_SUBMENU, // Folder row → enters OTA Tools sub-screen
@@ -322,6 +323,8 @@ private:
bool _importRequested; // set by key handler, cleared by main.cpp after calling import
#endif
bool _rxlogRequested = false; // set by key handler, cleared by main.cpp after opening Rx Log
// Channel share picker state
#define SHARE_MAX_CONTACTS 32
uint8_t _shareChannelIdx; // channel being shared
@@ -529,6 +532,9 @@ private:
addRow(ROW_EXPORT_IMPORT_SUBMENU);
#endif
// Rx Log packet sniffer (opens RxLogScreen)
addRow(ROW_RXLOG);
// Info section (stays at top level)
addRow(ROW_INFO_HEADER);
addRow(ROW_PUB_KEY);
@@ -673,6 +679,7 @@ private:
radio_set_params(_prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr);
radio_set_tx_power(_prefs->tx_power_dbm);
the_mesh.savePrefs();
the_mesh.resetRxPacketCount(); // zero the radio-page RX counter on radio param change
_radioChanged = false;
Serial.printf("Settings: Radio params applied - %.3f/%g/%d/%d TX:%d\n",
_prefs->freq, _prefs->bw, _prefs->sf, _prefs->cr, _prefs->tx_power_dbm);
@@ -931,6 +938,10 @@ public:
void clearImportRequest() { _importRequested = false; }
#endif
// Rx Log open request -- checked and cleared by main.cpp
bool isRxLogRequested() const { return _rxlogRequested; }
void clearRxLogRequest() { _rxlogRequested = false; }
// Channel share request -- checked and cleared by main.cpp
bool isShareRequested() const { return _shareRequested; }
int getShareContactIdx() const { return _shareContactIdx; }
@@ -2025,6 +2036,11 @@ public:
display.print("Channels >>");
break;
case ROW_RXLOG:
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
display.print("Rx Log >>");
break;
#ifdef HAS_SDCARD
case ROW_EXPORT_IMPORT_SUBMENU:
display.setColor(selected ? DisplayDriver::DARK : DisplayDriver::GREEN);
@@ -3749,6 +3765,10 @@ public:
Serial.println("Settings: entered Channels sub-screen");
break;
case ROW_RXLOG:
_rxlogRequested = true;
break;
case ROW_ADD_CHANNEL:
startEditText("");
break;
@@ -8,6 +8,7 @@
#include "PathEditorScreen.h"
#include "DiscoveryScreen.h"
#include "LastHeardScreen.h"
#include "RxLogScreen.h"
#include "Tracescreen.h"
#include "GamesMenuScreen.h"
#include "SnakeScreen.h"
@@ -808,6 +809,9 @@ public:
display.setCursor(0, 53);
sprintf(tmp, "Noise floor: %d", radio_driver.getNoiseFloor());
display.print(tmp);
display.setCursor(0, 64);
sprintf(tmp, "RX packets: %u", (unsigned)the_mesh.getRxPacketCount());
display.print(tmp);
#ifdef BLE_PIN_CODE
} else if (_page == HomePage::BLUETOOTH) {
display.setColor(DisplayDriver::GREEN);
@@ -1458,6 +1462,7 @@ void UITask::begin(DisplayDriver* display, SensorManager* sensors, NodePrefs* no
path_editor = nullptr; // Lazy-initialized on first use from contacts screen
discovery_screen = new DiscoveryScreen(this, &rtc_clock);
last_heard_screen = new LastHeardScreen(&rtc_clock);
rxlog_screen = new RxLogScreen(this, &rtc_clock);
trace_screen = new TraceScreen(this, &rtc_clock);
games_menu_screen = new GamesMenuScreen(this);
snake_screen = new SnakeScreen(this, &rtc_clock);
@@ -3231,6 +3236,16 @@ void UITask::gotoLastHeardScreen() {
_next_refresh = 100;
}
void UITask::gotoRxLogScreen() {
((RxLogScreen*)rxlog_screen)->resetScroll();
setCurrScreen(rxlog_screen);
if (_display != NULL && !_display->isOn()) {
_display->turnOn();
}
_auto_off = millis() + AUTO_OFF_MILLIS;
_next_refresh = 100;
}
void UITask::gotoTraceScreen() {
TraceScreen* ts = (TraceScreen*)trace_screen;
ts->enter(the_mesh.getNodePrefs()->path_hash_mode);
+4
View File
@@ -100,6 +100,7 @@ class UITask : public AbstractUITask {
UIScreen* path_editor; // Custom path editor screen (lazy-init)
UIScreen* discovery_screen; // Node discovery scan screen
UIScreen* last_heard_screen; // Last heard passive advert list
UIScreen* rxlog_screen; // Rx Log packet sniffer
UIScreen* trace_screen; // Trace path screen (standalone trace tool)
UIScreen* games_menu_screen; // Games launcher menu
UIScreen* snake_screen; // Snake game screen
@@ -205,6 +206,7 @@ public:
void gotoPathEditor(int contactIdx); // Navigate to custom path editor
void gotoDiscoveryScreen(); // Navigate to node discovery scan
void gotoLastHeardScreen(); // Navigate to last heard passive list
void gotoRxLogScreen(); // Navigate to Rx Log packet sniffer
void gotoTraceScreen(); // Navigate to trace path screen
void gotoGamesMenu(); // Navigate to games launcher menu
void gotoSnakeScreen(); // Navigate to snake game
@@ -266,6 +268,7 @@ public:
bool isOnPathEditor() const { return curr == path_editor; }
bool isOnDiscoveryScreen() const { return curr == discovery_screen; }
bool isOnLastHeardScreen() const { return curr == last_heard_screen; }
bool isOnRxLogScreen() const { return curr == rxlog_screen; }
bool isOnTraceScreen() const { return curr == trace_screen; }
bool isOnGamesMenu() const { return curr == games_menu_screen; }
bool isOnSnakeScreen() const { return curr == snake_screen; }
@@ -354,6 +357,7 @@ public:
UIScreen* getPathEditorScreen() const { return path_editor; }
UIScreen* getDiscoveryScreen() const { return discovery_screen; }
UIScreen* getLastHeardScreen() const { return last_heard_screen; }
UIScreen* getRxLogScreen() const { return rxlog_screen; }
UIScreen* getTraceScreen() const { return trace_screen; }
UIScreen* getGamesMenuScreen() const { return games_menu_screen; }
UIScreen* getSnakeScreen() const { return snake_screen; }