mirror of
https://github.com/liquidraver/ZephCore.git
synced 2026-09-01 21:08:19 +00:00
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:
@@ -826,7 +826,7 @@ void CompanionMesh::queueLocalSentContactMessage(const ContactInfo &contact,
|
||||
}
|
||||
|
||||
void CompanionMesh::queueLocalSentChannelMessage(uint8_t channel_idx,
|
||||
uint32_t timestamp, const char *text)
|
||||
uint32_t timestamp, const char *text, bool heard_repeat)
|
||||
{
|
||||
if (!text) return;
|
||||
uint8_t frame[MAX_FRAME_SIZE];
|
||||
@@ -846,12 +846,13 @@ void CompanionMesh::queueLocalSentChannelMessage(uint8_t channel_idx,
|
||||
put_le32(&frame[i], timestamp);
|
||||
i += 4;
|
||||
|
||||
/* Channel wire-text is "<sender_name>: <body>" — the same prefix that
|
||||
* BaseChatMesh::sendGroupMessage applied before transmitting over LoRa.
|
||||
* The phone app parses up to the first ':' as the sender, the rest as
|
||||
* the body, so we need to mirror that format here or the message
|
||||
* shows up empty with the sender slot empty too. */
|
||||
int n = snprintf((char *)&frame[i], sizeof(frame) - i, "%s: ", prefs.node_name);
|
||||
/* Channel wire-text is "<sender_name>: <body>" — the same prefix
|
||||
* BaseChatMesh::sendGroupMessage applied over LoRa. Prepend a
|
||||
* heard/unheard marker so the phone app can distinguish whether the
|
||||
* mesh propagated our flood. */
|
||||
const char *marker = heard_repeat ? "(>>\xe2\x9c\x93) " /* (>>✓) UTF-8 */
|
||||
: "(>>\xe2\x9c\x97) "; /* (>>✗) UTF-8 */
|
||||
int n = snprintf((char *)&frame[i], sizeof(frame) - i, "%s%s: ", marker, prefs.node_name);
|
||||
if (n < 0) n = 0;
|
||||
if ((size_t)n > sizeof(frame) - i) n = sizeof(frame) - i;
|
||||
i += n;
|
||||
@@ -862,7 +863,8 @@ void CompanionMesh::queueLocalSentChannelMessage(uint8_t channel_idx,
|
||||
memcpy(&frame[i], text, text_len);
|
||||
i += text_len;
|
||||
|
||||
LOG_DBG("queueLocalSentChannelMessage: frame_len=%d channel_idx=%d", i, channel_idx);
|
||||
LOG_DBG("queueLocalSentChannelMessage: frame_len=%d channel_idx=%d heard=%d",
|
||||
i, channel_idx, (int)heard_repeat);
|
||||
queueOfflineMessage(frame, i);
|
||||
sendPush(PUSH_CODE_MSG_WAITING);
|
||||
}
|
||||
|
||||
@@ -203,10 +203,13 @@ public:
|
||||
|
||||
/**
|
||||
* Queue a locally-originated channel message into the BLE offline queue
|
||||
* and signal MSG_WAITING. See queueLocalSentContactMessage().
|
||||
* and signal MSG_WAITING. The body is rendered as
|
||||
* "<heard-marker> <node_name>: <text>" — heard_repeat picks
|
||||
* "(>>✓) " (at least one neighbor repeated the flood) vs "(>>✗) "
|
||||
* (no repeats heard within the joystick UI's feedback window).
|
||||
*/
|
||||
void queueLocalSentChannelMessage(uint8_t channel_idx, uint32_t timestamp,
|
||||
const char *text);
|
||||
const char *text, bool heard_repeat);
|
||||
|
||||
/* DataStoreHost interface */
|
||||
bool onContactLoaded(const ContactInfo &c) override;
|
||||
|
||||
@@ -502,7 +502,7 @@ int BaseChatMesh::sendCommandData(const ContactInfo &recipient, uint32_t timesta
|
||||
}
|
||||
|
||||
bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel &channel,
|
||||
const char *sender_name, const char *text, int text_len)
|
||||
const char *sender_name, const char *text, int text_len, uint32_t *out_hash)
|
||||
{
|
||||
uint8_t temp[5 + MAX_TEXT_LEN + 32];
|
||||
memcpy(temp, ×tamp, 4);
|
||||
@@ -518,6 +518,15 @@ bool BaseChatMesh::sendGroupMessage(uint32_t timestamp, mesh::GroupChannel &chan
|
||||
|
||||
mesh::Packet *pkt = createGroupDatagram(PAYLOAD_TYPE_GRP_TXT, channel, temp, 5 + prefix_len + text_len);
|
||||
if (pkt) {
|
||||
if (out_hash) {
|
||||
/* Stash the FNV-1a hash for the caller and pre-register it with
|
||||
* the contention tracker so heard retransmits get counted. The
|
||||
* hash is content-only (payload_type + first 8 payload bytes),
|
||||
* unaffected by sendFlood's later path-hash bookkeeping. */
|
||||
uint32_t h = mesh::ContentionTracker::computePacketHash32(pkt);
|
||||
*out_hash = h;
|
||||
getContentionTracker().trackRetransmit(h, (uint32_t)_ms->getMillis());
|
||||
}
|
||||
sendFloodScoped(channel, pkt);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -171,8 +171,13 @@ public:
|
||||
uint32_t &expected_ack, uint32_t &est_timeout);
|
||||
int sendCommandData(const ContactInfo &recipient, uint32_t timestamp, uint8_t attempt, const char *text,
|
||||
uint32_t &est_timeout);
|
||||
/* @param out_hash if non-null, filled with the FNV-1a packet hash so the
|
||||
* caller can later query the contention tracker for "how many neighbors
|
||||
* heard and retransmitted this?" — used by the joystick UI's send-feedback
|
||||
* mechanism. When provided, the packet is also pre-registered with the
|
||||
* contention tracker (so heard dupes match). */
|
||||
bool sendGroupMessage(uint32_t timestamp, mesh::GroupChannel &channel, const char *sender_name,
|
||||
const char *text, int text_len);
|
||||
const char *text, int text_len, uint32_t *out_hash = nullptr);
|
||||
bool sendGroupData(mesh::GroupChannel &channel, uint8_t *path, uint8_t path_len,
|
||||
uint16_t data_type, const uint8_t *data, int data_len);
|
||||
int sendLogin(const ContactInfo &recipient, const char *password, uint32_t &est_timeout);
|
||||
|
||||
@@ -47,3 +47,11 @@
|
||||
|
||||
/* List view constants */
|
||||
#define UI_RECENT_LIST_SIZE 4 /* max items visible in a scrollable list */
|
||||
|
||||
/* UI-only path_len status markers (no wire-format meaning beyond OUT_PATH_SENT,
|
||||
* which is exposed to the BLE app). These two encode the outcome of a local
|
||||
* channel send for the joystick UI's _ch_previews ring buffer:
|
||||
* OUT_PATH_SENT_HEARD — at least one neighbor repeated the flood
|
||||
* OUT_PATH_SENT_UNHEARD — feedback window elapsed without hearing a repeat */
|
||||
#define OUT_PATH_SENT_HEARD 0xFD
|
||||
#define OUT_PATH_SENT_UNHEARD 0xFC
|
||||
|
||||
@@ -240,6 +240,106 @@ ContactInfo *JoystickUITask::tryMatchPendingAck(uint32_t ack)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/* ===== Channel-send feedback ===== */
|
||||
|
||||
void JoystickUITask::pendingChannelFeedbackCb(struct k_timer *t)
|
||||
{
|
||||
PendingChannelSend *slot = (PendingChannelSend *)k_timer_user_data_get(t);
|
||||
if (!slot || !slot->task) return;
|
||||
slot->feedback_due = true;
|
||||
if (s_signal_fn) s_signal_fn();
|
||||
}
|
||||
|
||||
int JoystickUITask::allocPendingChannelSlot()
|
||||
{
|
||||
for (int i = 0; i < MAX_PENDING_CHANNEL_SENDS; i++) {
|
||||
if (!_pending_channel_sends[i].active) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void JoystickUITask::completePendingChannelSend(int slot_idx, bool heard)
|
||||
{
|
||||
PendingChannelSend &s = _pending_channel_sends[slot_idx];
|
||||
|
||||
/* Update the local _ch_previews entry's path_len so the UI shows the
|
||||
* outcome ("sent+" / "sent?"). Match by stored ring index + timestamp
|
||||
* to defend against ring wrap-around. */
|
||||
if (s.preview_index >= 0 && s.preview_index < JOYSTICK_OFFLINE_QUEUE_SIZE &&
|
||||
_ch_previews[s.preview_index].timestamp == s.preview_timestamp) {
|
||||
_ch_previews[s.preview_index].path_len = heard ? OUT_PATH_SENT_HEARD : OUT_PATH_SENT_UNHEARD;
|
||||
}
|
||||
|
||||
/* Mirror to BLE app now (deferred until outcome is known) with the
|
||||
* heard/unheard prefix. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentChannelMessage(s.channel_idx, s.timestamp, s.text, heard);
|
||||
}
|
||||
|
||||
s.active = false;
|
||||
s.feedback_due = false;
|
||||
_next_refresh = 0;
|
||||
}
|
||||
|
||||
void JoystickUITask::processPendingChannelFeedback()
|
||||
{
|
||||
for (int i = 0; i < MAX_PENDING_CHANNEL_SENDS; i++) {
|
||||
PendingChannelSend &s = _pending_channel_sends[i];
|
||||
if (!s.active || !s.feedback_due) continue;
|
||||
s.feedback_due = false;
|
||||
int dupe_count = _mesh ? _mesh->getContentionTracker().extractDupeCount(s.hash) : -1;
|
||||
bool heard = (dupe_count > 0);
|
||||
completePendingChannelSend(i, heard);
|
||||
}
|
||||
}
|
||||
|
||||
bool JoystickUITask::startPendingChannel(uint8_t channel_idx, ChannelDetails &ch,
|
||||
uint32_t ts, const char *text)
|
||||
{
|
||||
if (!text || !_mesh) return false;
|
||||
int slot_idx = allocPendingChannelSlot();
|
||||
if (slot_idx < 0) {
|
||||
showAlert("Channel queue full", 1500);
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t pkt_hash = 0;
|
||||
int text_len = (int)strlen(text);
|
||||
bool ok = _mesh->sendGroupMessage(ts, ch.channel,
|
||||
_prefs ? _prefs->node_name : "", text, text_len, &pkt_hash);
|
||||
if (!ok) return false;
|
||||
ui_signal_tx();
|
||||
|
||||
/* Local preview now (with "sent" path_len; we'll rewrite to
|
||||
* SENT_HEARD/SENT_UNHEARD on feedback). */
|
||||
_ch_preview_head = (_ch_preview_head + 1) % JOYSTICK_OFFLINE_QUEUE_SIZE;
|
||||
ChannelMsgPreview &p = _ch_previews[_ch_preview_head];
|
||||
strncpy(p.channel, ch.name, sizeof(p.channel) - 1);
|
||||
p.channel[sizeof(p.channel) - 1] = '\0';
|
||||
strncpy(p.text, text, sizeof(p.text) - 1);
|
||||
p.text[sizeof(p.text) - 1] = '\0';
|
||||
p.timestamp = ts;
|
||||
p.path_len = OUT_PATH_SENT;
|
||||
if (_ch_preview_count < JOYSTICK_OFFLINE_QUEUE_SIZE) _ch_preview_count++;
|
||||
|
||||
PendingChannelSend &s = _pending_channel_sends[slot_idx];
|
||||
s.active = true;
|
||||
s.feedback_due = false;
|
||||
s.channel_idx = channel_idx;
|
||||
s.preview_index = _ch_preview_head;
|
||||
s.preview_timestamp = ts;
|
||||
s.timestamp = ts;
|
||||
s.hash = pkt_hash;
|
||||
size_t tl = (size_t)text_len;
|
||||
if (tl > MAX_TEXT_LEN) tl = MAX_TEXT_LEN;
|
||||
memcpy(s.text, text, tl);
|
||||
s.text[tl] = '\0';
|
||||
|
||||
k_timer_stop(&s.feedback_timer);
|
||||
k_timer_start(&s.feedback_timer, K_MSEC(CHANNEL_FEEDBACK_WINDOW_MS), K_NO_WAIT);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool joystick_queue_initialized;
|
||||
|
||||
#define ENTER_LONG_PRESS_MS 500
|
||||
@@ -408,6 +508,15 @@ void JoystickUITask::begin(BaseChatMesh *mesh, mesh::ZephyrRTCClock *rtc, NodePr
|
||||
k_timer_user_data_set(&_pending_sends[i].retry_timer, &_pending_sends[i]);
|
||||
}
|
||||
|
||||
/* Channel-send feedback timers. */
|
||||
for (int i = 0; i < MAX_PENDING_CHANNEL_SENDS; i++) {
|
||||
_pending_channel_sends[i].task = this;
|
||||
_pending_channel_sends[i].active = false;
|
||||
_pending_channel_sends[i].feedback_due = false;
|
||||
k_timer_init(&_pending_channel_sends[i].feedback_timer, pendingChannelFeedbackCb, NULL);
|
||||
k_timer_user_data_set(&_pending_channel_sends[i].feedback_timer, &_pending_channel_sends[i]);
|
||||
}
|
||||
|
||||
/* Init key queue */
|
||||
k_msgq_init(&_key_queue, _key_buf, sizeof(char), JOYSTICK_KEY_QUEUE_DEPTH);
|
||||
joystick_queue_initialized = true;
|
||||
@@ -655,6 +764,7 @@ void JoystickUITask::loop()
|
||||
|
||||
/* Pending-DM retry timers may have fired in ISR. */
|
||||
processPendingRetries();
|
||||
processPendingChannelFeedback();
|
||||
|
||||
/* Dequeue key events */
|
||||
char key = 0;
|
||||
@@ -1019,25 +1129,10 @@ bool JoystickUITask::sendComposedMessage(const char *text)
|
||||
ChannelDetails ch;
|
||||
if (!_mesh->getChannel(_compose_channel_idx, ch)) return false;
|
||||
uint32_t ts = _rtc ? _rtc->getCurrentTimeUnique() : k_uptime_get_32();
|
||||
bool ok = _mesh->sendGroupMessage(ts, ch.channel,
|
||||
_prefs ? _prefs->node_name : "", text, (int)strlen(text));
|
||||
if (ok) {
|
||||
ui_signal_tx();
|
||||
_ch_preview_head = (_ch_preview_head + 1) % JOYSTICK_OFFLINE_QUEUE_SIZE;
|
||||
ChannelMsgPreview &p = _ch_previews[_ch_preview_head];
|
||||
strncpy(p.channel, ch.name, sizeof(p.channel) - 1);
|
||||
p.channel[sizeof(p.channel) - 1] = '\0';
|
||||
strncpy(p.text, text, sizeof(p.text) - 1);
|
||||
p.text[sizeof(p.text) - 1] = '\0';
|
||||
p.timestamp = ts;
|
||||
p.path_len = OUT_PATH_SENT;
|
||||
if (_ch_preview_count < JOYSTICK_OFFLINE_QUEUE_SIZE) _ch_preview_count++;
|
||||
/* Notify BLE app so it can mirror the sent message in its UI. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentChannelMessage((uint8_t)_compose_channel_idx, ts, text);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
/* Pending-channel machinery handles broadcast, local preview, and
|
||||
* the deferred BLE-app mirror (queued only after the feedback
|
||||
* window decides heard / not-heard). */
|
||||
return startPendingChannel((uint8_t)_compose_channel_idx, ch, ts, text);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -1049,16 +1144,7 @@ bool JoystickUITask::sendChannelMessage(const char *text)
|
||||
ChannelDetails ch;
|
||||
if (!_mesh->getChannel(_compose_channel_idx, ch)) return false;
|
||||
uint32_t ts = _rtc ? _rtc->getCurrentTimeUnique() : k_uptime_get_32();
|
||||
bool ok = _mesh->sendGroupMessage(ts, ch.channel,
|
||||
_prefs ? _prefs->node_name : "", text, (int)strlen(text));
|
||||
if (ok) {
|
||||
ui_signal_tx();
|
||||
/* Notify BLE app so it can mirror the sent message in its UI. */
|
||||
if (CompanionMesh *cm = static_cast<CompanionMesh *>(_mesh)) {
|
||||
cm->queueLocalSentChannelMessage((uint8_t)_compose_channel_idx, ts, text);
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
return startPendingChannel((uint8_t)_compose_channel_idx, ch, ts, text);
|
||||
}
|
||||
|
||||
bool JoystickUITask::findContactByName(const char *name, ContactInfo &contact)
|
||||
|
||||
@@ -274,6 +274,31 @@ private:
|
||||
void doPendingSend(int slot_idx);
|
||||
void completePendingSend(int slot_idx);
|
||||
void processPendingRetries();
|
||||
|
||||
/* Channel-send feedback: after broadcasting a group message we wait
|
||||
* CHANNEL_FEEDBACK_WINDOW_MS to see if any neighbor repeated our flood
|
||||
* (queried via ContentionTracker::extractDupeCount). Outcome decides
|
||||
* the body prefix in the BLE-app mirror and the path_len indicator in
|
||||
* the local _ch_previews entry. */
|
||||
static const int MAX_PENDING_CHANNEL_SENDS = 4;
|
||||
static constexpr uint32_t CHANNEL_FEEDBACK_WINDOW_MS = 5000;
|
||||
struct PendingChannelSend {
|
||||
JoystickUITask *task;
|
||||
bool active;
|
||||
volatile bool feedback_due;
|
||||
uint8_t channel_idx;
|
||||
int preview_index; /* index in _ch_previews ring to update */
|
||||
uint32_t preview_timestamp; /* extra match guard if ring wrapped */
|
||||
uint32_t timestamp;
|
||||
uint32_t hash;
|
||||
char text[MAX_TEXT_LEN + 1];
|
||||
struct k_timer feedback_timer;
|
||||
};
|
||||
PendingChannelSend _pending_channel_sends[MAX_PENDING_CHANNEL_SENDS];
|
||||
static void pendingChannelFeedbackCb(struct k_timer *t);
|
||||
int allocPendingChannelSlot();
|
||||
void processPendingChannelFeedback();
|
||||
void completePendingChannelSend(int slot_idx, bool heard);
|
||||
public:
|
||||
/* Initiate a DM with retry tracking (called by sendComposedMessage). */
|
||||
bool startPendingDM(ContactInfo &recipient, uint32_t ts, const char *text);
|
||||
@@ -281,6 +306,10 @@ public:
|
||||
* Returns the recipient ContactInfo* if matched (so BaseChatMesh can do
|
||||
* its return-path-retry housekeeping), nullptr otherwise. */
|
||||
ContactInfo *tryMatchPendingAck(uint32_t ack);
|
||||
/* Initiate a channel send with feedback tracking. Returns true on
|
||||
* successful broadcast; the body of the message is stored locally as
|
||||
* "pending" until the feedback window expires. */
|
||||
bool startPendingChannel(uint8_t channel_idx, ChannelDetails &ch, uint32_t ts, const char *text);
|
||||
private:
|
||||
|
||||
/* Discover signal cache, owned here, populated via onRepeaterDiscoverResp */
|
||||
|
||||
@@ -169,6 +169,14 @@ static inline void formatHopCount(uint8_t path_len, char *out, size_t out_len)
|
||||
snprintf(out, out_len, "sent");
|
||||
return;
|
||||
}
|
||||
if (path_len == OUT_PATH_SENT_HEARD) {
|
||||
snprintf(out, out_len, "sent+");
|
||||
return;
|
||||
}
|
||||
if (path_len == OUT_PATH_SENT_UNHEARD) {
|
||||
snprintf(out, out_len, "sent?");
|
||||
return;
|
||||
}
|
||||
int hops = (int)(path_len & 63);
|
||||
if (hops <= 0) snprintf(out, out_len, "direct");
|
||||
else snprintf(out, out_len, "%dh", hops);
|
||||
|
||||
@@ -26,6 +26,14 @@ public:
|
||||
/* Returns true if packet matched a tracked retransmit (dupe recorded). */
|
||||
bool recordDupeIfTracked(uint32_t hash32, uint32_t now_ms);
|
||||
|
||||
/* Capture the dupe count for the given hash and remove the entry from
|
||||
* active tracking (also folds the sample into the EMA exactly once,
|
||||
* same as natural finalization would have). Returns the dupe count, or
|
||||
* -1 if the entry isn't found (already finalized, or never tracked).
|
||||
* Used by the joystick UI to ask "did my channel send get repeated?"
|
||||
* a few seconds after sending. */
|
||||
int extractDupeCount(uint32_t hash32);
|
||||
|
||||
/* Returns backoff_multiplier * airtime, clamped by remaining headroom.
|
||||
* Returns 0 when hard cap reached or backoff disabled. */
|
||||
uint16_t getReactiveHeadroom(uint32_t hash32, uint32_t airtime_ms) const;
|
||||
|
||||
@@ -34,10 +34,13 @@ class Mesh : public Dispatcher {
|
||||
void routeDirectRecvAcks(Packet *packet, uint32_t delay_millis);
|
||||
DispatcherAction forwardMultipartDirect(Packet *pkt);
|
||||
|
||||
public:
|
||||
/* Made public so the UI layer can query/extract per-packet dupe counts
|
||||
* for outbound-flood feedback (joystick channel-send "heard a repeat?"). */
|
||||
class ContentionTracker& getContentionTracker() { return _contention; }
|
||||
const class ContentionTracker& getContentionTracker() const { return _contention; }
|
||||
protected:
|
||||
ContentionTracker _contention;
|
||||
ContentionTracker& getContentionTracker() { return _contention; }
|
||||
const ContentionTracker& getContentionTracker() const { return _contention; }
|
||||
#ifdef CONFIG_ZEPHCORE_APC
|
||||
PowerController _power_ctrl;
|
||||
PowerController& getPowerController() { return _power_ctrl; }
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user