diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index c9f8f04e..e512a453 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -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)) { diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index c94b24dd..553df867 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -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 diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 881e5a53..2065b558 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -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"); diff --git a/examples/companion_radio/ui-new/Repeateradminscreen.h b/examples/companion_radio/ui-new/Repeateradminscreen.h index f652110b..dba9c241 100644 --- a/examples/companion_radio/ui-new/Repeateradminscreen.h +++ b/examples/companion_radio/ui-new/Repeateradminscreen.h @@ -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[] = { diff --git a/examples/companion_radio/ui-new/RxLogScreen.h b/examples/companion_radio/ui-new/RxLogScreen.h new file mode 100644 index 00000000..a38e461f --- /dev/null +++ b/examples/companion_radio/ui-new/RxLogScreen.h @@ -0,0 +1,216 @@ +#pragma once + +#include +#include +#include + +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; + } +}; diff --git a/examples/companion_radio/ui-new/Settingsscreen.h b/examples/companion_radio/ui-new/Settingsscreen.h index a13dcbfc..afed1cab 100644 --- a/examples/companion_radio/ui-new/Settingsscreen.h +++ b/examples/companion_radio/ui-new/Settingsscreen.h @@ -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; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 1ecbe229..1d835f9a 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -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); diff --git a/examples/companion_radio/ui-new/UITask.h b/examples/companion_radio/ui-new/UITask.h index 89569d5d..e673a0bb 100644 --- a/examples/companion_radio/ui-new/UITask.h +++ b/examples/companion_radio/ui-new/UITask.h @@ -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; }