Keep ESP-NOW TX seen keys instead of duplicate packet snapshots

This commit is contained in:
mikecarper committed 2026-10-23 18:57:57 -07:00
1 parent e13d24020a
commit b8929c15fe
8 files changed
+171 -21

No files matched your search

+24 -9
View File
@@ -53,6 +53,15 @@ inline bool routeHashPrefixesOverlap(const uint8_t* a, uint8_t a_len,
class SimpleMeshTables : public mesh::MeshTables {
public:
// A queued transport can retire an unsent admission after its Packet pool
// entry is gone. Keep the complete hash and its separate ACK-cache domain.
struct SeenKey {
uint8_t hash[MAX_HASH_SIZE] = {};
bool dedicated_ack = false;
};
static_assert(sizeof(SeenKey) == MAX_HASH_SIZE + 1,
"A seen key must not retain packet data or alignment padding");
// Recent rows belong to the mesh loop, including CLI requests marshaled from
// network tasks. They must not be read or changed concurrently by an ISR or
// worker. Byte access avoids unaligned uint32_t loads in the compact rows;
@@ -411,26 +420,32 @@ public:
}
}
void clear(const mesh::Packet* packet) override {
if (isDedicatedAckPacket(packet)) {
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
clearAckHash(hash);
SeenKey seenKey(const mesh::Packet& packet) const {
SeenKey key;
packet.calculatePacketHash(key.hash);
key.dedicated_ack = isDedicatedAckPacket(&packet);
return key;
}
void clearSeenKey(const SeenKey& key) {
if (key.dedicated_ack) {
clearAckHash(key.hash);
return;
}
uint8_t hash[MAX_HASH_SIZE];
packet->calculatePacketHash(hash);
uint8_t* sp = _hashes;
for (int i = 0; i < MAX_PACKET_HASHES; i++, sp += MAX_HASH_SIZE) {
if (memcmp(hash, sp, MAX_HASH_SIZE) == 0) {
if (memcmp(key.hash, sp, MAX_HASH_SIZE) == 0) {
memset(sp, 0, MAX_HASH_SIZE);
break;
}
}
}
void clear(const mesh::Packet* packet) override {
clearSeenKey(seenKey(*packet));
}
uint32_t getNumDirectDups() const { return _direct_dups; }
uint32_t getNumFloodDups() const { return _flood_dups; }
+10 -5
View File
@@ -304,7 +304,8 @@ void ESPNowBridge::end() {
uint8_t tx_index = _tx_tail;
for (uint8_t i = 0; i < _tx_count; ++i) {
const QueuedTransmit& queued_tx = _tx_queue[tx_index];
if (!queued_tx.started && !queued_tx.diagnostic) _seen_packets.clear(&queued_tx.packet);
if (!queued_tx.started && !queued_tx.diagnostic)
_seen_packets.clearSeenKey(queued_tx.seen_key);
tx_index = static_cast<uint8_t>((tx_index + 1) % TX_QUEUE_DEPTH);
}
portENTER_CRITICAL(&_tx_mux);
@@ -385,12 +386,16 @@ bool ESPNowBridge::queueTransmitFrames(
}
}
// Hash the caller-owned snapshot before entering the callback-shared queue
// lock. SHA work never extends an ESP-NOW critical section.
SimpleMeshTables::SeenKey seen_key;
if (!diagnostic) seen_key = _seen_packets.seenKey(packet);
bool queued = false;
portENTER_CRITICAL(&_tx_mux);
if (_tx_count < TX_QUEUE_DEPTH) {
QueuedTransmit& queued_tx = _tx_queue[_tx_head];
queued_tx.frames = frames;
if (!diagnostic) queued_tx.packet = packet;
queued_tx.seen_key = seen_key;
queued_tx.next_frame = 0;
queued_tx.started = false;
queued_tx.start_retries = 0;
@@ -417,7 +422,7 @@ void ESPNowBridge::pumpTransmitQueue() {
bool completed = false;
int completed_status = ESP_NOW_SEND_SUCCESS;
bool clear_unsent_seen = false;
mesh::Packet unsent_packet;
SimpleMeshTables::SeenKey unsent_key;
uint32_t dropped = 0;
const uint32_t now = millis();
bool timed_out = false;
@@ -527,7 +532,7 @@ void ESPNowBridge::pumpTransmitQueue() {
_tx_retry_at = now + TX_RETRY_DELAY_MS;
} else {
if (!queued_tx.started && !queued_tx.diagnostic) {
unsent_packet = queued_tx.packet;
unsent_key = queued_tx.seen_key;
clear_unsent_seen = true;
}
_tx_tail = static_cast<uint8_t>((_tx_tail + 1) % TX_QUEUE_DEPTH);
@@ -539,7 +544,7 @@ void ESPNowBridge::pumpTransmitQueue() {
portEXIT_CRITICAL(&_tx_mux);
// Admission marks the packet to suppress duplicate queue entries. Restore
// the prior behavior when even its first esp_now_send() cannot start.
if (clear_unsent_seen) _seen_packets.clear(&unsent_packet);
if (clear_unsent_seen) _seen_packets.clearSeenKey(unsent_key);
BRIDGE_DEBUG_PRINTLN("TX failed to start, error=%d\n", (int)result);
} else {
portENTER_CRITICAL(&_tx_mux);
+4 -1
View File
@@ -104,12 +104,15 @@ private:
// instead of transmitting an orphan fragment.
struct QueuedTransmit {
mesh::espnow::ESPNowRawFrames frames;
mesh::Packet packet;
SimpleMeshTables::SeenKey seen_key;
uint8_t next_frame;
bool started;
uint8_t start_retries;
bool diagnostic;
};
static_assert(sizeof(QueuedTransmit) <= sizeof(mesh::espnow::ESPNowRawFrames) +
sizeof(SimpleMeshTables::SeenKey) + 8,
"ESP-NOW TX must retain encoded frames without a second packet copy");
QueuedTransmit _tx_queue[TX_QUEUE_DEPTH];
portMUX_TYPE _tx_mux = portMUX_INITIALIZER_UNLOCKED;
volatile uint8_t _tx_head;