joystick UI: channel-send heard-repeat feedback

After broadcasting a group message, wait 5s to see if any neighbor
repeated the flood and use the outcome to mark the on-device entry
and the BLE-app mirror.

  - ContentionTracker gets extractDupeCount(hash): finds the tracked
    entry, captures dupe_count, finalizes (folds into EMA, marks
    inactive), returns the count or -1.
  - BaseChatMesh::sendGroupMessage gains an optional out_hash param;
    when set, the FNV-1a packet hash is also pre-registered with the
    contention tracker so heard retransmits get counted (originated
    floods weren't tracked before, only relays).
  - Mesh::getContentionTracker() promoted to public so the UI can
    query after the feedback window.

JoystickUITask grows a 4-slot pending-channel table with per-slot
k_timer (5s one-shot). startPendingChannel() broadcasts, adds the
local _ch_previews entry with path_len = OUT_PATH_SENT, and starts
the feedback timer. The timer ISR sets a feedback_due flag; the
loop's processPendingChannelFeedback() picks it up, calls
extractDupeCount(), and rewrites the preview's path_len to
OUT_PATH_SENT_HEARD (0xFD) or OUT_PATH_SENT_UNHEARD (0xFC) — which
formatHopCount renders as "sent+" / "sent?".

The deferred BLE-app mirror queues only on outcome with body prefix
"(>>✓) " (heard) or "(>>✗) " (not heard). queueLocalSentChannelMessage
gains a heard_repeat parameter for the selection.

Both channel send entry points (sendComposedMessage's channel branch
and sendChannelMessage) now route through startPendingChannel().
This commit is contained in:
liquidraver
2026-05-21 22:24:26 +02:00
parent 8e5e54a61e
commit 03fcc60fa9
11 changed files with 213 additions and 43 deletions
+9
View File
@@ -99,6 +99,15 @@ bool ContentionTracker::recordDupeIfTracked(uint32_t hash32, uint32_t now_ms)
return true;
}
int ContentionTracker::extractDupeCount(uint32_t hash32)
{
int idx = findEntry(hash32);
if (idx < 0) return -1;
int count = (int)_ring[idx].dupe_count;
finalizeEntry(idx); /* folds into EMA, marks inactive */
return count;
}
uint16_t ContentionTracker::getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const
{
int idx = findEntry(hash32);