main.cpp: fixed the “only sends once” bug by replacing last_tx_second with a UTC minute key, so Bob/CY/DAN can send once per active peer every minute at their scheduled second.
TBeamSupremeLoRaInterface.cpp: added RSSI/SNR to receive-side PHY logging, including accepted frames, malformed frames, and simulated PHY drops.
main.cpp: added RSSI/SNR to RX ANNOUNCE and inbound RX LINK establishment logs.
README.md: added updated RSSI/SNR examples and a table for RNSLINKREQ, RNSPROOF_DELAY, RNSPROOF, RNSLRRTT, RNSLINKRX, and RNSDEC.
This commit is contained in:
John Poole 2026-06-03 20:04:54 -07:00
commit 16507c54e8
3 changed files with 52 additions and 11 deletions

View file

@ -50,10 +50,11 @@ Substantive events retain the Exercise 204 style so multi-unit log parsing can c
```text ```text
TX ANNOUNCE: Bob TX ANNOUNCE: Bob
RX ANNOUNCE: label=Cy hash=<destination hash> phy=Cy(2) RX PHY: phy=2 len=... RSSI=-73.5 SNR=9.2 frames=...
RX ANNOUNCE: label=Cy hash=<destination hash> phy=Cy(2) RSSI=-73.5 SNR=9.2
TX LINKREQUEST: opening link to Cy slot=19 attempt=1/3 TX LINKREQUEST: opening link to Cy slot=19 attempt=1/3
LINK ACTIVE: initiator link established to Cy hash=<link hash> LINK ACTIVE: initiator link established to Cy hash=<link hash>
RX LINK: inbound link established hash=<link hash> phy=Bob(1) RX LINK: inbound link established hash=<link hash> phy=Bob(1) RSSI=-74.0 SNR=8.8
TX LINK: BOB says Hi to CY iter=0 via=outbound hash=<link hash> status=2 TX LINK: BOB says Hi to CY iter=0 via=outbound hash=<link hash> status=2
RX LINK: CY says Hi to BOB iter=0 | phy=Cy(2) RSSI=... SNR=... RX LINK: CY says Hi to BOB iter=0 | phy=Cy(2) RSSI=... SNR=...
LINK RETRY: no establishment after 60000 ms; retrying Cy attempts=1/3 LINK RETRY: no establishment after 60000 ms; retrying Cy attempts=1/3
@ -61,6 +62,19 @@ LINK FAILED: peer=Cy attempts=3 window_ms=... waiting_for_announce=1
LINK RETRY RESET: fresh announce from Cy LINK RETRY RESET: fresh announce from Cy
``` ```
`RX PHY` is emitted once per accepted LoRa frame before Reticulum decrypts or routes it. It is the broadest signal-strength record and includes frames that become announces, link setup packets, encrypted Link payloads, keepalives, or proofs. `RX PHY BAD` and `SIM PHY DROP` also include RSSI/SNR when malformed or intentionally blocked frames are seen.
The following `RNS...` prefixes are generated by the linked microReticulum tree when Arduino link instrumentation is enabled:
| Prefix | Source | Meaning |
| --- | --- | --- |
| `RNSLINKREQ` | `microReticulum/src/Link.cpp` | Incoming Link request validation accepted; shows the new link ID, owner destination, hops, status, initiator flag, interface, and whether the app has a link-established callback. |
| `RNSPROOF_DELAY` | `microReticulum/src/Link.cpp` | Link proof send path is applying the configured proof delay before transmitting the proof packet. |
| `RNSPROOF` | `microReticulum/src/Link.cpp` and transport proof probes | Link proof validation and proof forwarding diagnostics, including signature validity, state transitions, interface checks, and exceptions. |
| `RNSLRRTT` | `microReticulum/src/Link.cpp` | Link request round-trip-time packet handling. This is part of Link establishment and marks decrypt, active, and owner callback events. |
| `RNSLINKRX` | `microReticulum/src/Link.cpp` | Link-associated packet receive path. It logs packet context, decrypt success, app packet callback entry/return, no-callback cases, and LRRTT dispatch. |
| `RNSDEC` | `microReticulum/src/Link.cpp` | Link encryption/decryption token diagnostics, including encrypt attempts, decrypt attempts, and decrypt failure class. |
Reticulum library logging is set to warning level in this exercise. Heap, path-store, entries, and byte-count diagnostics are intentionally suppressed so serial logs remain focused on field-test results. Reticulum library logging is set to warning level in this exercise. Heap, path-store, entries, and byte-count diagnostics are intentionally suppressed so serial logs remain focused on field-test results.
# Build, Upload, And Monitor # Build, Upload, And Monitor

View file

@ -103,6 +103,12 @@ void TBeamSupremeLoRaInterface::loop() {
_radio->startReceive(); _radio->startReceive();
return; return;
} }
Serial.printf("RX PHY: phy=%u len=%d RSSI=%.1f SNR=%.1f frames=%lu\r\n",
(unsigned)physical_tx,
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_rx_frames);
if (len <= 1) { if (len <= 1) {
_radio->startReceive(); _radio->startReceive();
return; return;
@ -158,21 +164,33 @@ bool TBeamSupremeLoRaInterface::unpack_frame(uint8_t* frame, int& len, uint8_t&
#if SIM_PHY_ENVELOPE #if SIM_PHY_ENVELOPE
if (len < PHY_ENVELOPE_LEN + 1) { if (len < PHY_ENVELOPE_LEN + 1) {
++_phy_bad_frames; ++_phy_bad_frames;
Serial.printf("RX PHY BAD: reason=short len=%d RSSI=%.1f SNR=%.1f bad=%lu\r\n",
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_bad_frames);
DEBUGF("SIM PHY malformed: short frame len=%d", len); DEBUGF("SIM PHY malformed: short frame len=%d", len);
return false; return false;
} }
if (frame[0] != PHY_MAGIC_0 || frame[1] != PHY_MAGIC_1 || frame[2] != PHY_VERSION) { if (frame[0] != PHY_MAGIC_0 || frame[1] != PHY_MAGIC_1 || frame[2] != PHY_VERSION) {
++_phy_bad_frames; ++_phy_bad_frames;
Serial.printf("RX PHY BAD: reason=envelope len=%d RSSI=%.1f SNR=%.1f bad=%lu\r\n",
len,
_last_rssi,
_last_snr,
(unsigned long)_phy_bad_frames);
DEBUGF("SIM PHY malformed: bad envelope len=%d", len); DEBUGF("SIM PHY malformed: bad envelope len=%d", len);
return false; return false;
} }
physical_tx = frame[3]; physical_tx = frame[3];
if (should_drop_physical_tx(physical_tx)) { if (should_drop_physical_tx(physical_tx)) {
++_phy_blocked_frames; ++_phy_blocked_frames;
Serial.printf("SIM PHY DROP: rx=%u tx=%u len=%d blocked=%lu\r\n", Serial.printf("SIM PHY DROP: rx=%u tx=%u len=%d RSSI=%.1f SNR=%.1f blocked=%lu\r\n",
(unsigned)NODE_SLOT_INDEX, (unsigned)NODE_SLOT_INDEX,
(unsigned)physical_tx, (unsigned)physical_tx,
len, len,
_last_rssi,
_last_snr,
(unsigned long)_phy_blocked_frames); (unsigned long)_phy_blocked_frames);
DEBUGF("SIM PHY DROP: rx=%u tx=%u len=%d", DEBUGF("SIM PHY DROP: rx=%u tx=%u len=%d",
(unsigned)NODE_SLOT_INDEX, (unsigned)NODE_SLOT_INDEX,

View file

@ -123,7 +123,7 @@ struct PeerState {
uint32_t last_rx_ms = 0; uint32_t last_rx_ms = 0;
uint32_t last_tx_ms = 0; uint32_t last_tx_ms = 0;
uint32_t tx_iter = 0; uint32_t tx_iter = 0;
uint8_t last_tx_second = 255; uint32_t last_tx_minute = 0xFFFFFFFFUL;
}; };
static PeerState peers[MAX_PEERS]; static PeerState peers[MAX_PEERS];
@ -714,7 +714,7 @@ static void clear_peer_slot(uint8_t index) {
peers[index].last_rx_ms = 0; peers[index].last_rx_ms = 0;
peers[index].last_tx_ms = 0; peers[index].last_tx_ms = 0;
peers[index].tx_iter = 0; peers[index].tx_iter = 0;
peers[index].last_tx_second = 255; peers[index].last_tx_minute = 0xFFFFFFFFUL;
} }
static int ensure_peer_for_label(const String& label) { static int ensure_peer_for_label(const String& label) {
@ -893,10 +893,14 @@ static void on_inbound_link_established(RNS::Link& link) {
peers[peer_index].last_link_active_ms = millis(); peers[peer_index].last_link_active_ms = millis();
} }
uint8_t physical_tx = lora_impl ? lora_impl->last_physical_tx() : 255; uint8_t physical_tx = lora_impl ? lora_impl->last_physical_tx() : 255;
Serial.printf("RX LINK: inbound link established hash=%s phy=%s(%u)\r\n", float rssi = lora_impl ? lora_impl->last_rssi() : 0.0f;
float snr = lora_impl ? lora_impl->last_snr() : 0.0f;
Serial.printf("RX LINK: inbound link established hash=%s phy=%s(%u) RSSI=%.1f SNR=%.1f\r\n",
link.hash().toHex().c_str(), link.hash().toHex().c_str(),
node_label_for_slot(physical_tx), node_label_for_slot(physical_tx),
(unsigned)physical_tx); (unsigned)physical_tx,
rssi,
snr);
show_status("LINK ACTIVE", "inbound", link.hash().toHex().c_str()); show_status("LINK ACTIVE", "inbound", link.hash().toHex().c_str());
} }
@ -941,11 +945,15 @@ class LinkAnnounceHandler : public RNS::AnnounceHandler {
} }
uint8_t physical_tx = lora_impl ? lora_impl->last_physical_tx() : 255; uint8_t physical_tx = lora_impl ? lora_impl->last_physical_tx() : 255;
Serial.printf("RX ANNOUNCE: label=%s hash=%s phy=%s(%u)\r\n", float rssi = lora_impl ? lora_impl->last_rssi() : 0.0f;
float snr = lora_impl ? lora_impl->last_snr() : 0.0f;
Serial.printf("RX ANNOUNCE: label=%s hash=%s phy=%s(%u) RSSI=%.1f SNR=%.1f\r\n",
peers[peer_index].label.c_str(), peers[peer_index].label.c_str(),
peers[peer_index].destination_hash.toHex().c_str(), peers[peer_index].destination_hash.toHex().c_str(),
node_label_for_slot(physical_tx), node_label_for_slot(physical_tx),
(unsigned)physical_tx); (unsigned)physical_tx,
rssi,
snr);
show_status("RX ANNOUNCE", peers[peer_index].label.c_str(), peers[peer_index].destination_hash.toHex().c_str()); show_status("RX ANNOUNCE", peers[peer_index].label.c_str(), peers[peer_index].destination_hash.toHex().c_str());
} }
}; };
@ -1224,9 +1232,10 @@ void loop() {
maybe_send_scheduled_announce(); maybe_send_scheduled_announce();
const uint8_t send_slot = node_send_slot_second(); const uint8_t send_slot = node_send_slot_second();
if (have_rtc_now && rtc_now.second == send_slot) { if (have_rtc_now && rtc_now.second == send_slot) {
const uint32_t tx_minute = (uint32_t)(to_epoch_seconds(rtc_now) / 60LL);
for (uint8_t i = 0; i < MAX_PEERS; ++i) { for (uint8_t i = 0; i < MAX_PEERS; ++i) {
PeerState& peer = peers[i]; PeerState& peer = peers[i];
if (peer.label.length() == 0 || peer.last_tx_second == rtc_now.second) { if (peer.label.length() == 0 || peer.last_tx_minute == tx_minute) {
continue; continue;
} }
@ -1242,7 +1251,7 @@ void loop() {
continue; continue;
} }
peer.last_tx_second = rtc_now.second; peer.last_tx_minute = tx_minute;
peer.last_tx_ms = now; peer.last_tx_ms = now;
const char* recipient = board_id_for_label(peer.label); const char* recipient = board_id_for_label(peer.label);
String message = String(BOARD_ID) + " says Hi to " + recipient + " iter=" + String(peer.tx_iter++); String message = String(BOARD_ID) + " says Hi to " + recipient + " iter=" + String(peer.tx_iter++);